[
  {
    "path": ".github/contributing.md",
    "content": "# Contributing to chibivue\n\nAre you looking at this page now because you're interested in contributing to chibivue? If so, I'd be very pleased.  \nI'd appreciate it if you could create some pull request, no matter how minor.  \nI've written some guides below on how to contribute, so please take a moment to read them.\nThank you to all chibivue fans. 💖\n\nubugeeei.\n\n---\n\n## Guide to the main directories/files\n\nFirst, let's talk about the top-level directories:\n\n```sh\nbook # Contains materials related to the online book\n\nimpl # Contains the latest source code of chibivue packages (runtime-core, runtime-dom, reactivity, compiler-core, compiler-dom, compiler-sfc, compiler-vapor, runtime-vapor, server-renderer, etc.)\n\nexamples # Contains sample code using the packages. Not directly related to the online book.\n\ntools # Development and reader tools for the online book.\n\n.github # Contains CI configuration files and contribution guides.\n```\n\nNow, let's take a closer look at the book directory.\n\n```sh\nbook\n  |- images # Contains image files used in the online book.\n  |- online-book # The main body of the online book. It is a Vitepress project.\n  |- impls # Contains the source code for each chapter.\n```\n\n## Guide about the method of contribution\n\n### Before submitting a pull request\n\n### Forking the repository\n\nAccess https://github.com/chibivue-land/chibivue and click on the `Fork` button on this page to fork it to your own account.\n\nYou can choose any name for the repository. Feel free to set other information as well.\n\n### Setting up the local environment\n\n#### Installing the necessary tools\n\n- [Node.js](https://nodejs.org/en) (v24+)\n- [pnpm](https://pnpm.io/) (v10+)\n\n### Getting Started\n\nFirst, install the dependencies and set up the playground.\n\n```sh\npnpm i && pnpm setup\n```\n\nThen, you can start the development server.\n\n```sh\npnpm dev\n```\n\n### Available Scripts\n\n| Script | Description |\n|--------|-------------|\n| **Book** | |\n| `dev` | Start online book dev server |\n| `build` | Build online book |\n| `preview` | Preview built online book |\n| `lint:text` | Lint book text |\n| **Setup** | |\n| `setup` | Install dependencies and generate playground |\n| `setup:dev` | Generate playground files to examples/playground |\n| `setup:vue` | Set up Vue.js core comparison environment |\n| `setup:book` | Generate chibivue implementation for book readers |\n| **Implementation** | |\n| `impl:dev` | Start playground dev server |\n| `impl:dev:app` | Start app example dev server |\n| `impl:dev:vapor` | Start vapor mode example dev server |\n| `impl:dev:vue` | Start Vue.js core dev server for comparison |\n| `impl:build` | Build all packages |\n| `impl:clean` | Remove all dist folders |\n| `impl:check` | Run all checks (lint, fmt, typecheck, build, test) |\n| **Quality** | |\n| `check` | Run type checking (tsgo) |\n| `lint` | Run linter (oxlint) |\n| `lint:fix` | Run linter with auto-fix |\n| `fmt` | Format code (oxfmt) |\n| `fmt:check` | Check code formatting |\n| `test` | Run tests once |\n| `test:watch` | Run tests in watch mode |\n\n### Running book chapter implementations\n\nIf you want to run the source code for each chapter, you can do so with the following command.\n\n```sh\ncd book/impls/${section-name}/${chapter-name}\npnpm dev\n```\n\n### Book Playground\n\nThe project includes a WebContainer-based playground (`book/playground`) that allows readers to try each chapter's implementation directly in the browser.\n\nTo start the playground:\n\n```sh\npnpm play           # Start the playground dev server\n```\n\nThe playground supports:\n- Selecting different chapters to explore\n- Editing code with Monaco editor\n- Running the development server in the browser\n- Persisting edits to localStorage\n- Resetting files to their original state\n\nIf you modify chapter implementations in `book/impls/`, run `pnpm play:generate` to update the playground data.\n\n#### Creating a branch (start making changes)\n\nClone the forked repository and create a branch.\n\nBefore creating a branch, make sure the upstream is set to the main branch.\n\nAs for the branch name, if it is related to a specific issue, please use the format `${issue-number}-${description (kebab-case)}`.  \nPlease make the description as clear and unique as possible.\nThere are no strict rules at the moment, but please avoid very short names or names that are too generic (lack uniqueness).  \nIf it is not related to a specific issue, the issue number is not necessary.\n\nIt would be helpful if you could create an issue whenever possible. It is not necessary for simple typo fixes.  \nAlso, if the changes are expected to be significant or critical in content, it would be appreciated if you could consult with @ubugeeei in advance.  \n(If such changes are made without consultation, there is a possibility that the PR will be rejected depending on the case.)\n\n### Creating commits\n\nThere are no strict rules regarding the content and granularity of commit messages to the working branch.\n\nFor commit messages, we have provided a configuration file for git-cz (changelog.config.cjs), so it is recommended to use it (but not required).\n\nIf you want to use git-cz, you need to install it locally.\n\n```sh\nnpm install -g git-cz\n```\n\n```sh\n# When you run the cz command with the staged changes, an interactive shell will start.\ngit cz\n```\n\nOf course, if you have any suggestions for the git-cz configuration file, please feel free to send a PR.\n\nRegarding commit messages, if the issue number is included in the branch name, the issue number will be automatically included in the commit message.  \nThis is achieved by husky, and you can check the details in `/.husky/commit-msg`.\n\n### Creating a Pull Request\n\nOnce you have finished making changes locally and pushed the changes, please create a Pull Request to the main branch of https://github.com/chibivue-land/chibivue.  \nPlease make the title and description of the Pull Request as clear as possible.  \nPlease always notify @ubugeeei when the work is completed to keep track of whether the work is in progress or completed.  \nYou can mention @ubugeeei in the PR comment. The same applies when the changes are completed after the review.\nAlso, please make sure to check that the CI has succeeded before reporting completion.\n\nBasically, all PRs are managed by @ubugeeei, so please contact @ubugeeei for any inquiries.\n\n## Guide about the contents of contribution\n\nThis is a guide to the changes you make. Here are a few points to keep in mind.\n\n- When making changes to the online book, please make sure that the content is consistent across all language versions (English, Japanese, Simplified Chinese, Traditional Chinese).\n- When making changes to the source code of each chapter, please appropriately incorporate those changes into the source code of subsequent chapters.\n- When including images, figures, or text from other sources, please make sure to provide proper attribution.\n"
  },
  {
    "path": ".github/workflows/check.yml",
    "content": "name: ci\n\non:\n  push:\n    branches:\n      - '**'\n\npermissions:\n  contents: read\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: 'package.json'\n\n      - uses: pnpm/action-setup@v4\n        name: Install pnpm\n\n      - name: Get pnpm store directory\n        shell: bash\n        run: echo \"STORE_PATH=$(pnpm store path --silent)\" >> $GITHUB_ENV\n\n      - uses: actions/cache@v4\n        name: Setup pnpm cache\n        with:\n          path: ${{ env.STORE_PATH }}\n          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-store-\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Install Playwright browsers\n        run: pnpm exec playwright install --with-deps chromium\n\n      - name: Check fmt\n        run: pnpm fmt:check\n\n      - name: Check lint\n        run: pnpm lint\n\n      - name: Check lint text\n        run: pnpm lint:text\n\n      - name: Check types\n        run: pnpm check\n\n      - name: Check impl building\n        run: pnpm impl:build\n\n      - name: Check test\n        run: pnpm test\n\n      - name: Build book\n        run: pnpm build\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy chibivue Book to Pages\n\non:\n  push:\n    branches: [main]\n\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\nconcurrency:\n  group: pages\n  cancel-in-progress: false\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Check if it's the original repo and the main branch\n        run: |\n          if [[ \"${{ github.repository }}\" == \"ubugeeei/chibivue\" && \"${{ github.ref }}\" == \"refs/heads/main\" ]]; then\n            echo \"This is the original repo's main branch, running the workflow.\"\n          else\n            echo \"This is a fork or a different branch, skipping the workflow.\"\n            exit 0\n          fi\n\n      - name: Setup Node\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: 'package.json'\n\n      - uses: pnpm/action-setup@v4\n        name: Install pnpm\n\n      - name: Get pnpm store directory\n        shell: bash\n        run: echo \"STORE_PATH=$(pnpm store path --silent)\" >> $GITHUB_ENV\n\n      - uses: actions/cache@v4\n        name: Setup pnpm cache\n        with:\n          path: ${{ env.STORE_PATH }}\n          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-store-\n\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Build with VitePress\n        run: pnpm build\n\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: book/online-book/.vitepress/dist\n\n  deploy:\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\nexamples/playground\nexamples/vuejs-core\n.env\ntools/translator/ja2en/input.md\ntools/translator/ja2en/output.md\nbook/online-book/.vitepress/cache\nbook/online-book/.vitepress/dist\n\n# for dts-bundle\ntemp\n\n# VSCode extension\n*.vsix\n.vscode-test\nout"
  },
  {
    "path": ".oxlintrc.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/oxc-project/oxlint/main/npm/oxlint/configuration_schema.json\",\n  \"ignorePatterns\": [\"examples/vuejs-core\"],\n  \"rules\": {\n    \"no-unused-vars\": \"off\",\n    \"no-unused-expressions\": \"off\",\n    \"no-useless-escape\": \"off\",\n    \"no-this-alias\": \"off\",\n    \"no-async-promise-executor\": \"off\",\n    \"only-used-in-recursion\": \"off\",\n    \"no-non-null-asserted-optional-chain\": \"off\",\n    \"no-wrapper-object-types\": \"off\",\n    \"unicorn/no-new-array\": \"off\",\n    \"unicorn/no-useless-spread\": \"off\",\n    \"unicorn/no-useless-fallback-in-spread\": \"off\"\n  }\n}\n"
  },
  {
    "path": ".textlintrc.json",
    "content": "{\n  \"rules\": {\n    \"ja-space-between-half-and-full-width\": {\n      \"space\": \"always\"\n    },\n    \"prh\": {\n      \"rulePaths\": [\"rules/prh-punctuation-mark.yml\"]\n    }\n  }\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"oxc.oxc-vscode\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"cSpell.words\": [\n    \"antfu\",\n    \"augmentor\",\n    \"chainsi\",\n    \"chibi\",\n    \"chibivue\",\n    \"citty\",\n    \"consola\",\n    \"cytoscape\",\n    \"deduped\",\n    \"deindent\",\n    \"estree\",\n    \"fizzbuzz\",\n    \"hyperscript\",\n    \"jiti\",\n    \"klass\",\n    \"lightningcss\",\n    \"nocheck\",\n    \"onwarn\",\n    \"oxfmt\",\n    \"oxlint\",\n    \"paren\",\n    \"Parens\",\n    \"RAWTEXT\",\n    \"RCDATA\",\n    \"resetsix\",\n    \"rolldown\",\n    \"textlint\",\n    \"tokei\",\n    \"unref\",\n    \"vdom\",\n    \"vitepress\",\n    \"vnode\",\n    \"Vnodes\",\n    \"vuejs\"\n  ],\n  \"material-icon-theme.folders.associations\": {\n    \"book\": \"resource\",\n    \"impls\": \"vue\",\n    \"online-book\": \"vuepress\",\n    \"vuejs-core\": \"vue\",\n    \"@extensions\": \"lib\"\n  },\n  \"material-icon-theme.files.associations\": {},\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"oxc.oxc-vscode\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.oxc\": \"explicit\"\n  },\n  \"oxc.enable\": true\n}\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "* @ubugeeei\n\n/book/online-book/src/zh-cn @resetsix\n/book/online-book/src/zh-tw @resetsix\n/book/online-book/.vitepress/config/zh-cn.ts @resetsix\n/book/online-book/.vitepress/config/zh-tw.ts @resetsix\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023-present ubugeeei\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README-zh-cn.md",
    "content": "<p align=\"center\">\n  <img src=\"./book/online-book/src/public/og.png\" width=\"480\">\n</p>\n\n<h1 align=\"center\">chibivue</h1>\n\n<p align=\"center\">\n  <b>编写 Vue.js：从一行 \"Hello, World\" 开始，逐步构建。</b>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://book.chibivue.land/zh-cn\">在线书籍</a> ·\n  <a href=\"https://discord.gg/aVHvmbmSRy\">Discord</a> ·\n  <a href=\"https://github.com/sponsors/ubugeeei\">赞助</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"./README.md\">English</a> ·\n  <a href=\"./README-zh-tw.md\">繁體中文</a>\n</p>\n\n---\n\n**chibivue** 是 [Vue.js](https://github.com/vuejs/core) 的最小化教学实现．\n\n- 响应式系统\n- 虚拟 DOM 和补丁渲染\n- 组件系统\n- 模板编译器\n- SFC 编译器\n- Vapor Mode（实验性）\n\n> \"chibi\" 在日语中意思是 \"小\"。\n\n## 在线书籍\n\n[![Pages Deploy](https://github.com/chibivue-land/chibivue/actions/workflows/deploy.yml/badge.svg?branch=main)](https://github.com/chibivue-land/chibivue/actions/workflows/deploy.yml)\n\n| 语言 | 链接 |\n|------|------|\n| English | https://book.chibivue.land |\n| 日本語 | https://book.chibivue.land/ja |\n| 简体中文 | https://book.chibivue.land/zh-cn |\n| 繁體中文 | https://book.chibivue.land/zh-tw |\n\n## 快速开始\n\n### 环境要求\n\n- [Node.js](https://nodejs.org/) v24+\n- [pnpm](https://pnpm.io/) v10+\n\n### 本地阅读书籍\n\n```sh\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\npnpm install\npnpm dev\n```\n\n### 尝试实现\n\n```sh\npnpm setup      # 生成 playground\npnpm impl:dev   # 启动开发服务器\n```\n\n## 附加章节\n\n**超极限超极端最小 Vue**\n\n仅用 **110 行**代码实现 createApp，虚拟 DOM，响应式，模板编译器和 SFC 编译器．\n\n[阅读章节](https://book.chibivue.land/zh-cn/bonus/hyper-ultimate-super-extreme-minimal-vue) · [查看源码](https://github.com/chibivue-land/chibivue/blob/main/book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/packages/index.ts)\n\n## 贡献\n\n请查看 [contributing.md](.github/contributing.md)．\n\n## 社区\n\n加入我们的 [Discord 服务器](https://discord.gg/aVHvmbmSRy) 参与讨论，提问和获取公告．\n\n---\n\n<div align=\"center\">\n\n## 赞助商\n\n<a href=\"https://github.com/sponsors/ubugeeei\">\n  <img src=\"https://raw.githubusercontent.com/ubugeeei/sponsors/main/sponsors.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n[成为赞助商](https://github.com/sponsors/ubugeeei)\n\n</div>\n"
  },
  {
    "path": "README-zh-tw.md",
    "content": "<p align=\"center\">\n  <img src=\"./book/online-book/src/public/og.png\" width=\"480\">\n</p>\n\n<h1 align=\"center\">chibivue</h1>\n\n<p align=\"center\">\n  <b>編寫 Vue.js：從一行 \"Hello, World\" 開始，逐步構建。</b>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://book.chibivue.land/zh-tw\">線上書籍</a> ·\n  <a href=\"https://discord.gg/aVHvmbmSRy\">Discord</a> ·\n  <a href=\"https://github.com/sponsors/ubugeeei\">贊助</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"./README.md\">English</a> ·\n  <a href=\"./README-zh-cn.md\">简体中文</a>\n</p>\n\n---\n\n**chibivue** 是 [Vue.js](https://github.com/vuejs/core) 的最小化教學實現．\n\n- 響應式系統\n- 虛擬 DOM 和補丁渲染\n- 組件系統\n- 模板編譯器\n- SFC 編譯器\n- Vapor Mode（實驗性）\n\n> \"chibi\" 在日語中意思是 \"小\"。\n\n## 線上書籍\n\n[![Pages Deploy](https://github.com/chibivue-land/chibivue/actions/workflows/deploy.yml/badge.svg?branch=main)](https://github.com/chibivue-land/chibivue/actions/workflows/deploy.yml)\n\n| 語言 | 連結 |\n|------|------|\n| English | https://book.chibivue.land |\n| 日本語 | https://book.chibivue.land/ja |\n| 简体中文 | https://book.chibivue.land/zh-cn |\n| 繁體中文 | https://book.chibivue.land/zh-tw |\n\n## 快速開始\n\n### 環境要求\n\n- [Node.js](https://nodejs.org/) v24+\n- [pnpm](https://pnpm.io/) v10+\n\n### 本地閱讀書籍\n\n```sh\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\npnpm install\npnpm dev\n```\n\n### 嘗試實現\n\n```sh\npnpm setup      # 生成 playground\npnpm impl:dev   # 啟動開發伺服器\n```\n\n## 附加章節\n\n**超極限超極端最小 Vue**\n\n僅用 **110 行**程式碼實現 createApp，虛擬 DOM，響應式，模板編譯器和 SFC 編譯器．\n\n[閱讀章節](https://book.chibivue.land/zh-tw/bonus/hyper-ultimate-super-extreme-minimal-vue) · [查看原始碼](https://github.com/chibivue-land/chibivue/blob/main/book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/packages/index.ts)\n\n## 貢獻\n\n請查看 [contributing.md](.github/contributing.md)．\n\n## 社群\n\n加入我們的 [Discord 伺服器](https://discord.gg/aVHvmbmSRy) 參與討論，提問和獲取公告．\n\n---\n\n<div align=\"center\">\n\n## 贊助商\n\n<a href=\"https://github.com/sponsors/ubugeeei\">\n  <img src=\"https://raw.githubusercontent.com/ubugeeei/sponsors/main/sponsors.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n[成為贊助商](https://github.com/sponsors/ubugeeei)\n\n</div>\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"./book/online-book/src/public/og.png\" width=\"480\">\n</p>\n\n<h1 align=\"center\">chibivue</h1>\n\n<p align=\"center\">\n  <b>Writing Vue.js: Step by Step, from just one line of \"Hello, World\".</b>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://book.chibivue.land\">Online Book</a> ·\n  <a href=\"https://discord.gg/aVHvmbmSRy\">Discord</a> ·\n  <a href=\"https://github.com/sponsors/ubugeeei\">Sponsor</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"./README-zh-cn.md\">简体中文</a> ·\n  <a href=\"./README-zh-tw.md\">繁體中文</a>\n</p>\n\n---\n\n**chibivue** is a minimal implementation of [Vue.js](https://github.com/vuejs/core) for educational purposes.\n\n- Reactivity System\n- Virtual DOM & Patch Rendering\n- Component System\n- Template Compiler\n- SFC Compiler\n- Vapor Mode (experimental)\n\n> \"chibi\" means \"small\" in Japanese.\n\n## Online Book\n\n[![Pages Deploy](https://github.com/chibivue-land/chibivue/actions/workflows/deploy.yml/badge.svg?branch=main)](https://github.com/chibivue-land/chibivue/actions/workflows/deploy.yml)\n\n| Language | URL |\n|----------|-----|\n| English | https://book.chibivue.land |\n| 日本語 | https://book.chibivue.land/ja |\n| 简体中文 | https://book.chibivue.land/zh-cn |\n| 繁體中文 | https://book.chibivue.land/zh-tw |\n\n## Quick Start\n\n### Requirements\n\n- [Node.js](https://nodejs.org/) v24+\n- [pnpm](https://pnpm.io/) v10+\n\n### Read the Book Locally\n\n```sh\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\npnpm install\npnpm dev\n```\n\n### Try the Implementation\n\n```sh\npnpm setup      # Generate playground\npnpm impl:dev   # Start dev server\n```\n\n## Bonus Track\n\n**Hyper Ultimate Super Extreme Minimal Vue**\n\nImplements createApp, Virtual DOM, Reactivity, Template Compiler, and SFC Compiler in just **110 lines**.\n\n[Read the Chapter](https://book.chibivue.land/bonus/hyper-ultimate-super-extreme-minimal-vue) · [View Source](https://github.com/chibivue-land/chibivue/blob/main/book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/packages/index.ts)\n\n## Contributing\n\nSee [contributing.md](.github/contributing.md).\n\n## Community\n\nJoin our [Discord Server](https://discord.gg/aVHvmbmSRy) for discussions, questions, and announcements.\n\n---\n\n<div align=\"center\">\n\n## Sponsors\n\n<a href=\"https://github.com/sponsors/ubugeeei\">\n  <img src=\"https://raw.githubusercontent.com/ubugeeei/sponsors/main/sponsors.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n[Become a Sponsor](https://github.com/sponsors/ubugeeei)\n\n</div>\n"
  },
  {
    "path": "book/figures-src/README.md",
    "content": "# Book figure assets\n\nThe online book serves figure assets from `book/online-book/src/public/figures`.\n\nUse paths that mirror the book structure:\n\n```txt\nfigures/<chapter>/<article>/<purpose>.<ext>\n```\n\nExamples:\n\n- `figures/_brand/logo.png`\n- `figures/_people/ubugeeei-avatar.jpg`\n- `figures/_sponsors/ubugeeei-sponsors.png`\n- `figures/10-minimum-example/reactivity/target-map-structure.svg`\n- `figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg`\n- `figures/50-basic-template-compiler/v-bind/transform-vbind-flow.svg`\n\nGenerated explanatory diagrams are SVG files created by `tools/book-figures/generate.mjs`.\nRegenerate them with:\n\n```sh\nnode tools/book-figures/generate.mjs\n```\n\nScreenshots should keep their closest article directory and use descriptive names such as `*-result.png`, `*-console.png`, or `*-flow.png`.\n\nLegacy pre-rebrand assets are archived here:\n\n- `legacy-drawio/`: old draw.io source files\n- `legacy-raster/`: old exported raster diagrams\n- `unreferenced/`: old files that are not currently used by the online book\n"
  },
  {
    "path": "book/figures-src/legacy-drawio/c1c2map.drawio",
    "content": "<mxfile host=\"65bd71144e\">\n    <diagram id=\"b-V9glVyOOTkLKbPg4Gc\" name=\"ページ1\">\n        <mxGraphModel dx=\"1515\" dy=\"590\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" background=\"#ffffff\" math=\"0\" shadow=\"0\">\n            <root>\n                <mxCell id=\"0\"/>\n                <mxCell id=\"1\" parent=\"0\"/>\n                <mxCell id=\"2\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"120\" y=\"270\" width=\"210\" height=\"350\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"3\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"145\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"5\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"145\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"6\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"145\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"7\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"145\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"8\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"460\" y=\"270\" width=\"210\" height=\"350\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"9\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"485\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"10\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"485\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"11\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"485\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"12\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"485\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"13\" value=\"C1\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"205\" y=\"220\" width=\"40\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"14\" value=\"C2\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"540\" y=\"220\" width=\"50\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"16\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" edge=\"1\" parent=\"1\" source=\"3\" target=\"9\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"620\" y=\"480\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"670\" y=\"430\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"17\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" edge=\"1\" parent=\"1\" source=\"5\" target=\"10\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"335\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"335\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"18\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" edge=\"1\" parent=\"1\" source=\"6\" target=\"11\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"415\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"415\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"19\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" edge=\"1\" parent=\"1\" source=\"7\" target=\"12\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"495\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"495\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"20\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"320\" y=\"280\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"21\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"320\" y=\"370\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"22\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"320\" y=\"440\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"23\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"320\" y=\"530\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n            </root>\n        </mxGraphModel>\n    </diagram>\n</mxfile>"
  },
  {
    "path": "book/figures-src/legacy-drawio/c1c2map_deleted.drawio",
    "content": "<mxfile host=\"65bd71144e\">\n    <diagram id=\"b-V9glVyOOTkLKbPg4Gc\" name=\"ページ1\">\n        <mxGraphModel dx=\"1515\" dy=\"590\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" background=\"#ffffff\" math=\"0\" shadow=\"0\">\n            <root>\n                <mxCell id=\"0\"/>\n                <mxCell id=\"1\" parent=\"0\"/>\n                <mxCell id=\"2\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"120\" y=\"270\" width=\"210\" height=\"350\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"3\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"5\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"6\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"7\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"8\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"460\" y=\"270\" width=\"210\" height=\"350\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"9\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"10\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"11\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"12\" value=\"????\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;dashed=1;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"13\" value=\"C1\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"205\" y=\"220\" width=\"40\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"14\" value=\"C2\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"540\" y=\"220\" width=\"50\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"16\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"3\" target=\"9\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"620\" y=\"480\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"670\" y=\"430\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"17\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"5\" target=\"10\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"335\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"335\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"18\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"6\" target=\"11\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"415\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"415\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"19\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"7\" target=\"12\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"495\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"495\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"20\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"280\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"21\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"370\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"22\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"440\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"23\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;???&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"375\" y=\"530\" width=\"40\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"26\" value=\"\" style=\"curved=1;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;\" edge=\"1\" parent=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"590\" y=\"320\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"710\" y=\"240\" as=\"targetPoint\"/>\n                        <Array as=\"points\">\n                            <mxPoint x=\"620\" y=\"240\"/>\n                        </Array>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"27\" value=\"patch loop start\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"700\" y=\"220\" width=\"180\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"28\" value=\"\" style=\"curved=1;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;\" edge=\"1\" parent=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"620\" y=\"490\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"750\" y=\"520\" as=\"targetPoint\"/>\n                        <Array as=\"points\">\n                            <mxPoint x=\"650\" y=\"540\"/>\n                        </Array>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"30\" value=\"patch loop end\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"750\" y=\"500\" width=\"160\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n            </root>\n        </mxGraphModel>\n    </diagram>\n</mxfile>"
  },
  {
    "path": "book/figures-src/legacy-drawio/c1c2map_inserted.drawio",
    "content": "<mxfile host=\"65bd71144e\">\n    <diagram id=\"b-V9glVyOOTkLKbPg4Gc\" name=\"ページ1\">\n        <mxGraphModel dx=\"1595\" dy=\"621\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" background=\"#ffffff\" math=\"0\" shadow=\"0\">\n            <root>\n                <mxCell id=\"0\"/>\n                <mxCell id=\"1\" parent=\"0\"/>\n                <mxCell id=\"2\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"120\" y=\"270\" width=\"210\" height=\"350\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"3\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"5\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"6\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"7\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"8\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"460\" y=\"270\" width=\"210\" height=\"430\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"9\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"10\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"11\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"13\" value=\"C1\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"205\" y=\"220\" width=\"40\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"14\" value=\"C2\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"540\" y=\"220\" width=\"50\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"16\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"3\" target=\"9\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"620\" y=\"480\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"670\" y=\"430\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"17\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"5\" target=\"31\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"335\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"335\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"18\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"6\" target=\"10\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"415\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"415\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"19\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"7\" target=\"11\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"495\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"485\" y=\"645\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"20\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"280\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"21\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"370\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"22\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"440\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"31\" value=\"new element\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"485\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"32\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"485\" y=\"610\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"33\" value=\"create new node\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"750\" y=\"650\" width=\"180\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"28\" value=\"\" style=\"curved=1;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;\" parent=\"1\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"620\" y=\"640\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"750\" y=\"670\" as=\"targetPoint\"/>\n                        <Array as=\"points\">\n                            <mxPoint x=\"650\" y=\"690\"/>\n                        </Array>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"35\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"320\" y=\"520\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n            </root>\n        </mxGraphModel>\n    </diagram>\n</mxfile>"
  },
  {
    "path": "book/figures-src/legacy-drawio/c1c2map_inserted_correct.drawio",
    "content": "<mxfile host=\"65bd71144e\">\n    <diagram id=\"b-V9glVyOOTkLKbPg4Gc\" name=\"ページ1\">\n        <mxGraphModel dx=\"1515\" dy=\"590\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" background=\"#ffffff\" math=\"0\" shadow=\"0\">\n            <root>\n                <mxCell id=\"0\"/>\n                <mxCell id=\"1\" parent=\"0\"/>\n                <mxCell id=\"2\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"120\" y=\"270\" width=\"210\" height=\"350\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"3\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"5\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"6\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"7\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"145\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"8\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"460\" y=\"270\" width=\"210\" height=\"430\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"9\" value=\"li 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"290\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"10\" value=\"li 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"450\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"11\" value=\"li 3\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"530\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"13\" value=\"C1\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"205\" y=\"220\" width=\"40\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"14\" value=\"C2\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"540\" y=\"220\" width=\"50\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"16\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"3\" target=\"9\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"620\" y=\"480\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"670\" y=\"430\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"17\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"5\" target=\"10\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"335\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"335\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"18\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;\" parent=\"1\" source=\"6\" target=\"11\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"415\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"495\" y=\"415\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"19\" value=\"\" style=\"endArrow=classic;startArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=-0.031;entryY=0.4;entryDx=0;entryDy=0;labelBackgroundColor=none;entryPerimeter=0;\" parent=\"1\" source=\"7\" target=\"32\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"315\" y=\"495\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"485\" y=\"645\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"20\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"320\" y=\"280\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"21\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"330\" y=\"420\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"22\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"330\" y=\"500\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"31\" value=\"new element\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"370\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"32\" value=\"li 4\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;labelBackgroundColor=none;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"485\" y=\"610\" width=\"160\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"33\" value=\"create new node\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"760\" y=\"420\" width=\"180\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"28\" value=\"\" style=\"curved=1;endArrow=classic;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;\" parent=\"1\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"630\" y=\"410\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"760\" y=\"440\" as=\"targetPoint\"/>\n                        <Array as=\"points\">\n                            <mxPoint x=\"660\" y=\"460\"/>\n                        </Array>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"34\" value=\"&lt;font style=&quot;font-size: 11px;&quot;&gt;Compare and patch diffs&lt;/font&gt;\" style=\"text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=20;fontFamily=Architects Daughter;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;labelBackgroundColor=none;\" vertex=\"1\" parent=\"1\">\n                    <mxGeometry x=\"330\" y=\"585\" width=\"150\" height=\"40\" as=\"geometry\"/>\n                </mxCell>\n            </root>\n        </mxGraphModel>\n    </diagram>\n</mxfile>"
  },
  {
    "path": "book/figures-src/legacy-drawio/reactive_observer.drawio",
    "content": "<mxfile host=\"65bd71144e\">\n    <diagram id=\"vGUKsrpaSU9UvXaOGbhk\" name=\"ページ1\">\n        <mxGraphModel dx=\"3242\" dy=\"2104\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" background=\"#ffffff\" math=\"0\" shadow=\"0\">\n            <root>\n                <mxCell id=\"0\"/>\n                <mxCell id=\"1\" parent=\"0\"/>\n                <mxCell id=\"2\" value=\"targetMap&lt;br&gt;(observers)\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"184\" y=\"170\" width=\"146\" height=\"70\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"3\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.75;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;\" parent=\"1\" source=\"8\" target=\"2\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"410\" y=\"370\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"520\" y=\"300\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"4\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.25;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;\" parent=\"1\" source=\"6\" target=\"2\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"150\" y=\"350\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"180\" y=\"280\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"5\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;\" parent=\"1\" source=\"7\" target=\"2\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"300\" y=\"380\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"251.5\" y=\"250\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"6\" value=\"object 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"90\" y=\"330\" width=\"110\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"7\" value=\"object 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"290\" y=\"340\" width=\"110\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"8\" value=\". . .\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"490\" y=\"340\" width=\"110\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"9\" value=\"key 1\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"-50\" y=\"480\" width=\"110\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"10\" value=\"key 2\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"180\" y=\"470\" width=\"110\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"11\" value=\". . .\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"359\" y=\"480\" width=\"110\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"12\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;\" parent=\"1\" source=\"11\" target=\"7\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"390\" y=\"460\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"390\" y=\"420\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"13\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.25;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;\" parent=\"1\" source=\"9\" target=\"6\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"424\" y=\"490\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"355\" y=\"410\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"14\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.75;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;\" parent=\"1\" source=\"10\" target=\"6\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"75\" y=\"460\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"155\" y=\"400\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"15\" value=\"Dep&lt;br&gt;&lt;font style=&quot;font-size: 11px;&quot;&gt;update()&lt;/font&gt;\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"-70\" y=\"570\" width=\"150\" height=\"80\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"17\" value=\"\" style=\"endArrow=none;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;\" parent=\"1\" source=\"15\" target=\"9\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"410\" y=\"420\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"460\" y=\"370\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"18\" value=\". . .\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"160\" y=\"570\" width=\"150\" height=\"60\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"19\" value=\"\" style=\"endArrow=none;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;\" parent=\"1\" source=\"18\" target=\"10\" edge=\"1\">\n                    <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"75\" y=\"620\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"75\" y=\"550\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n                <mxCell id=\"20\" value=\"Subject&lt;br&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;observ(), notify()&lt;/font&gt;\" style=\"rounded=0;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=20;\" parent=\"1\" vertex=\"1\">\n                    <mxGeometry x=\"122\" y=\"-60\" width=\"270\" height=\"150\" as=\"geometry\"/>\n                </mxCell>\n                <mxCell id=\"21\" value=\"\" style=\"endArrow=diamondThin;endFill=0;endSize=24;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;fontSize=16;entryX=0.5;entryY=1;entryDx=0;entryDy=0;\" parent=\"1\" source=\"2\" target=\"20\" edge=\"1\">\n                    <mxGeometry width=\"160\" relative=\"1\" as=\"geometry\">\n                        <mxPoint x=\"155\" y=\"340\" as=\"sourcePoint\"/>\n                        <mxPoint x=\"230.5\" y=\"250\" as=\"targetPoint\"/>\n                    </mxGeometry>\n                </mxCell>\n            </root>\n        </mxGraphModel>\n    </diagram>\n</mxfile>"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/examples/playground/src/main.ts",
    "content": "import \"chibivue\";\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/packages/index.ts",
    "content": "console.log(\"Hello chibivue!\");\n"
  },
  {
    "path": "book/impls/00_introduction/010_project_setup/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\nconst app = createApp({\n  render() {\n    return \"Hello world.\";\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/packages/index.ts",
    "content": "export type Options = {\n  render: () => string;\n};\n\nexport type App = {\n  mount: (selector: string) => void;\n};\n\nexport const createApp = (options: Options): App => {\n  return {\n    mount: (selector) => {\n      const root = document.querySelector(selector);\n      if (root) {\n        root.innerHTML = options.render();\n      }\n    },\n  };\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/010_create_app\", () => {\n  it(\"should render a message\", () => {\n    const app = createApp({\n      render() {\n        return \"Hello world.\";\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"Hello world.\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/010_create_app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\nconst app = createApp({\n  render() {\n    return \"Hello world.\";\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/index.ts",
    "content": "export * from \"./runtime-dom\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const message = rootComponent.render!();\n        render(message, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/component.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\n\nexport type Component = ComponentOptions;\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  render?: Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-core/renderer.ts",
    "content": "export type RootRenderFunction<HostElement = RendererElement> = (\n  message: string,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode> {\n  setElementText(node: HostNode, text: string): void;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const { setElementText: hostSetElementText } = options;\n\n  const render: RootRenderFunction = (message, container) => {\n    hostSetElementText(container, message);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\n\nconst { render } = createRenderer(nodeOps);\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: RendererOptions<Node> = {\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/015_package_architecture\", () => {\n  it(\"should render a message\", () => {\n    const app = createApp({\n      render() {\n        return \"Hello world.\";\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"Hello world.\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/015_package_architecture/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/examples/playground/src/main.ts",
    "content": "import { createApp, h } from \"chibivue\";\n\nconst app = createApp({\n  render() {\n    return h(\"div\", { id: \"my-app\" }, [\n      h(\"p\", { style: \"color: red; font-weight: bold;\" }, [\"Hello world.\"]),\n      h(\n        \"button\",\n        {\n          onClick() {\n            alert(\"Hello world!\");\n          },\n        },\n        [\"click me!\"],\n      ),\n    ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const vnode = rootComponent.render!();\n        render(vnode, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/component.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\n\nexport type Component = ComponentOptions;\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  render?: Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/h.ts",
    "content": "import type { VNode, VNodeProps } from \"./vnode\";\n\nexport function h(type: string, props: VNodeProps, children: (VNode | string)[]) {\n  return { type, props, children };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/renderer.ts",
    "content": "import type { VNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options;\n\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === \"string\") return hostCreateText(vnode);\n    const el = hostCreateElement(vnode.type);\n\n    Object.entries(vnode.props).forEach(([key, value]) => {\n      hostPatchProp(el, key, value);\n    });\n\n    for (const child of vnode.children) {\n      const childEl = renderVNode(child);\n      hostInsert(childEl, el);\n    }\n\n    return el;\n  }\n\n  const render: RootRenderFunction = (vnode, container) => {\n    const el = renderVNode(vnode);\n    hostInsert(el, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-core/vnode.ts",
    "content": "export interface VNode {\n  type: string;\n  props: VNodeProps;\n  children: (VNode | string)[];\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text: string) => {\n    return document.createTextNode(text);\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/020_simple_h_function\", () => {\n  it(\"should render with h function\", () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", { id: \"my-app\" }, [h(\"p\", {}, [\"Hello world.\"])]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe('<div id=\"my-app\"><p>Hello world.</p></div>');\n  });\n\n  it(\"should handle click events\", () => {\n    const onClick = vi.fn();\n    const app = createApp({\n      render() {\n        return h(\"button\", { id: \"btn\", onClick }, [\"click me\"]);\n      },\n    });\n    app.mount(\"#host\");\n\n    const btn = host.querySelector(\"#btn\") as HTMLButtonElement;\n    btn.click();\n    expect(onClick).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/020_simple_h_function/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return function render() {\n      return h(\"div\", { id: \"my-app\" }, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\"button\", { onClick: increment }, [\"increment\"]),\n      ]);\n    };\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/apiCreateApp.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const componentRender = rootComponent.setup!();\n\n        const updateComponent = () => {\n          const vnode = componentRender();\n          render(vnode, rootContainer);\n        };\n\n        const effect = new ReactiveEffect(updateComponent);\n        effect.run();\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/component.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\n\nexport type Component = ComponentOptions;\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  render?: Function;\n  setup?: () => Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/h.ts",
    "content": "import type { VNode, VNodeProps } from \"./vnode\";\n\nexport function h(type: string, props: VNodeProps, children: (VNode | string)[]) {\n  return { type, props, children };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/renderer.ts",
    "content": "import type { VNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options;\n\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === \"string\") return hostCreateText(vnode);\n    const el = hostCreateElement(vnode.type);\n\n    Object.entries(vnode.props).forEach(([key, value]) => {\n      hostPatchProp(el, key, value);\n    });\n\n    for (const child of vnode.children) {\n      const childEl = renderVNode(child);\n      hostInsert(childEl, el);\n    }\n\n    return el;\n  }\n\n  const render: RootRenderFunction = (vnode, container) => {\n    while (container.firstChild) container.removeChild(container.firstChild);\n    const el = renderVNode(vnode);\n    hostInsert(el, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-core/vnode.ts",
    "content": "export interface VNode {\n  type: string;\n  props: VNodeProps;\n  children: (VNode | string)[];\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text: string) => {\n    return document.createTextNode(text);\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/030_reactive_system\", () => {\n  it(\"should render reactive state\", () => {\n    const state = reactive({ count: 0 });\n    const onClick = vi.fn(() => {\n      state.count++;\n    });\n    const app = createApp({\n      setup() {\n        return function render() {\n          return h(\"div\", { id: \"my-app\" }, [\n            h(\"p\", {}, [`count: ${state.count}`]),\n            h(\"button\", { id: \"btn\", onClick }, [\"increment\"]),\n          ]);\n        };\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      '<div id=\"my-app\"><p>count: 0</p><button id=\"btn\">increment</button></div>',\n    );\n\n    const btn = host.querySelector(\"#btn\") as HTMLButtonElement;\n    btn.click();\n    expect(onClick).toHaveBeenCalled();\n    expect(host.innerHTML).toBe(\n      '<div id=\"my-app\"><p>count: 1</p><button id=\"btn\">increment</button></div>',\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/030_reactive_system/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return function render() {\n      return h(\"div\", { id: \"my-app\" }, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\"button\", { onClick: increment }, [\"increment\"]),\n      ]);\n    };\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/component.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\n\nexport type Component = ComponentOptions;\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  render?: Function;\n  setup?: () => Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport type { Component } from \"./component\";\nimport { Text, type VNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else {\n      processElement(n1, n2, container);\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const componentRender = rootComponent.setup!();\n\n    let n1: VNode | null = null;\n\n    const updateComponent = () => {\n      const n2 = componentRender();\n      patch(n1, n2, container);\n      n1 = n2;\n    };\n\n    const effect = new ReactiveEffect(updateComponent);\n    effect.run();\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-core/vnode.ts",
    "content": "export type VNodeTypes = string | typeof Text;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = { type, props, children: children, el: undefined };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/040_vdom_system\", () => {\n  it(\"should render reactive state (vdom patch)\", () => {\n    const state = reactive({ count: 0 });\n    const onClick = vi.fn(() => {\n      state.count++;\n    });\n    const app = createApp({\n      setup() {\n        return function render() {\n          return h(\"div\", { id: \"my-app\" }, [\n            h(\"p\", {}, [`count: ${state.count}`]),\n            h(\"button\", { id: \"btn\", onClick }, [\"increment\"]),\n          ]);\n        };\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      '<div id=\"my-app\"><p>count: 0</p><button id=\"btn\">increment</button></div>',\n    );\n\n    const btn = host.querySelector(\"#btn\") as HTMLButtonElement;\n    btn.click();\n    expect(onClick).toHaveBeenCalled();\n    expect(host.innerHTML).toBe(\n      '<div id=\"my-app\"><p>count: 1</p><button id=\"btn\">increment</button></div>',\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/040_vdom_system/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst CounterComponent = {\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\"button\", { onClick: increment }, [\"increment\"]),\n      ]);\n  },\n};\n\nconst app = createApp({\n  setup: () => {\n    return () =>\n      h(\"div\", { id: \"my-app\" }, [\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n    isMounted: false,\n  };\n\n  return instance;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  render?: Function;\n  setup?: () => Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  type InternalRenderFunction,\n  createComponentInstance,\n} from \"./component\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup() as InternalRenderFunction;\n    }\n\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render());\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/050_component_system\", () => {\n  it(\"should render component\", () => {\n    const state = reactive({ count: 0 });\n    const onClick = vi.fn(() => {\n      state.count++;\n    });\n    const Comp = {\n      setup() {\n        return () =>\n          h(\"div\", {}, [\n            h(\"p\", {}, [`count: ${state.count}`]),\n            h(\"button\", { id: \"btn\", onClick }, [\"increment\"]),\n          ]);\n      },\n    };\n    const App = createApp({\n      setup: () => {\n        return () => h(\"div\", { id: \"my-app\" }, [h(Comp, {}, [])]);\n      },\n    });\n    App.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      // prettier-ignore\n      '<div id=\"my-app\">' + \n        '<div>' + \n          '<p>count: 0</p>' + \n          '<button id=\"btn\">increment</button>' + \n        '</div>' + \n      '</div>',\n    );\n\n    const btn = host.querySelector(\"#btn\") as HTMLButtonElement;\n    btn.click();\n    expect(onClick).toHaveBeenCalled();\n    expect(host.innerHTML).toBe(\n      // prettier-ignore\n      '<div id=\"my-app\">' + \n        '<div>' + \n          '<p>count: 1</p>' + \n          '<button id=\"btn\">increment</button>' + \n        '</div>' + \n      '</div>',\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h(\"div\", { id: \"my-app\" }, [`message: ${props.message}`]);\n  },\n};\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: \"hello\" });\n    const changeMessage = () => {\n      state.message += \"!\";\n    };\n\n    return () =>\n      h(\"div\", { id: \"my-app\" }, [\n        h(MyComponent, { message: state.message }, []),\n        h(\"button\", { onClick: changeMessage }, [\"change message\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport type { Props } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n\n    isMounted: false,\n  };\n\n  return instance;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (props: Record<string, any>) => Function;\n  render?: Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.assign(props, rawProps);\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      if (options && options.hasOwnProperty(key)) {\n        props[key] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  type InternalRenderFunction,\n  createComponentInstance,\n} from \"./component\";\nimport { initProps, updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup(instance.props) as InternalRenderFunction;\n    }\n\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render());\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/050_component_system2\", () => {\n  it(\"should component props\", () => {\n    const state = reactive({ count: 0 });\n    const onClick = vi.fn(() => {\n      state.count++;\n    });\n    const Comp = {\n      props: { count: { type: Number } },\n      setup(props: { count: number }) {\n        return () => h(\"div\", {}, [`count: ${props.count}`]);\n      },\n    };\n\n    const App = createApp({\n      setup() {\n        return () =>\n          h(\"div\", { id: \"my-app\" }, [\n            h(Comp, { count: state.count }, []),\n            h(\"button\", { id: \"btn\", onClick }, [\"increment\"]),\n          ]);\n      },\n    });\n    App.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      // prettier-ignore\n      '<div id=\"my-app\">' + \n        '<div>count: 0</div>' + \n        '<button id=\"btn\">increment</button>' + \n      '</div>',\n    );\n\n    const btn = host.querySelector(\"#btn\") as HTMLButtonElement;\n    btn.click();\n    expect(onClick).toHaveBeenCalled();\n    expect(host.innerHTML).toBe(\n      // prettier-ignore\n      '<div id=\"my-app\">' + \n        '<div>count: 1</div>' + \n        '<button id=\"btn\">increment</button>' + \n      '</div>',\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system2/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`someMessage: ${props.someMessage}`]),\n        h(\"button\", { onClick: () => emit(\"click:change-message\") }, [\"change message\"]),\n      ]);\n  },\n};\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: \"hello\" });\n    const changeMessage = () => {\n      state.message += \"!\";\n    };\n\n    return () =>\n      h(\"div\", { id: \"my-app\" }, [\n        h(\n          MyComponent,\n          {\n            \"some-message\": state.message,\n            \"onClick:change-message\": changeMessage,\n          },\n          [],\n        ),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport type { Props } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function;\n  render?: Function;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\n\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  type InternalRenderFunction,\n  createComponentInstance,\n} from \"./component\";\nimport { initProps, updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup(instance.props, {\n        emit: instance.emit,\n      }) as InternalRenderFunction;\n    }\n\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render());\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/050_component_system3\", () => {\n  it(\"should component props and handle emitted event\", () => {\n    const state = reactive({ count: 0 });\n    const onClickIncrement = vi.fn(() => {\n      state.count++;\n    });\n    const Comp = {\n      props: { count: { type: Number } },\n      setup(props: { count: number }, { emit }: any) {\n        return () =>\n          h(\"div\", {}, [\n            h(\"div\", {}, [`count: ${props.count}`]),\n            h(\"button\", { id: \"btn\", onClick: () => emit(\"click:increment\") }, [\"increment\"]),\n          ]);\n      },\n    };\n\n    const App = createApp({\n      setup() {\n        return () =>\n          h(\"div\", { id: \"my-app\" }, [\n            h(Comp, { count: state.count, \"onClick:increment\": onClickIncrement }, []),\n          ]);\n      },\n    });\n    App.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      // prettier-ignore\n      '<div id=\"my-app\">' + \n        '<div>' +\n          '<div>count: 0</div>' + \n          '<button id=\"btn\">increment</button>' + \n        '</div>' +\n      '</div>',\n    );\n\n    const btn = host.querySelector(\"#btn\") as HTMLButtonElement;\n    btn.click();\n    expect(onClickIncrement).toHaveBeenCalled();\n    expect(host.innerHTML).toBe(\n      // prettier-ignore\n      '<div id=\"my-app\">' + \n        '<div>' +\n          '<div>count: 1</div>' + \n          '<button id=\"btn\">increment</button>' + \n        '</div>' +\n      '</div>',\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/050_component_system3/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\nconst app = createApp({\n  template: `<b class=\"hello\" style=\"color: red;\">Hello World!!</b>`,\n});\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  ATTRIBUTE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/codegen.ts",
    "content": "export const generate = ({\n  tag,\n  props,\n  textContent,\n}: {\n  tag: string;\n  props: Record<string, string>;\n  textContent: string;\n}): string => {\n  return `return () => {\n  const { h } = ChibiVue;\n  return h(\"${tag}\", { ${Object.entries(props)\n    .map(([k, v]) => `${k}: \"${v}\"`)\n    .join(\", \")} }, [\"${textContent}\"]);\n}`;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template);\n  const code = generate(parseResult);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/compiler-core/parse.ts",
    "content": "export const baseParse = (\n  content: string,\n): { tag: string; props: Record<string, string>; textContent: string } => {\n  const matched = content.match(/<(\\w+)\\s+([^>]*)>([^<]*)<\\/\\1>/);\n  if (!matched) return { tag: \"\", props: {}, textContent: \"\" };\n\n  const [_, tag, attrs, textContent] = matched;\n\n  const props: Record<string, string> = {};\n  attrs.replace(/(\\w+)=[\"']([^\"']*)[\"']/g, (_, key: string, value: string) => {\n    props[key] = value;\n    return \"\";\n  });\n\n  return { tag, props, textContent };\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/compiler-dom/index.ts",
    "content": "import { baseCompile } from \"../compiler-core\";\n\nexport function compile(template: string) {\n  return baseCompile(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n  }\n\n  // ------------------------ ここ\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render());\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/060_template_compiler\", () => {\n  it(\"should render template option\", () => {\n    const app = createApp({\n      template: `<b class=\"hello\" style=\"color: red;\">Hello World!!</b>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe('<b class=\"hello\" style=\"color: red;\">Hello World!!</b>');\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n\n  `,\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  ATTRIBUTE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/codegen.ts",
    "content": "import { type ElementNode, NodeTypes, type TemplateChildNode, type TextNode } from \"./ast\";\n\nexport const generate = ({ children }: { children: TemplateChildNode[] }): string => {\n  return `return function render() {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`;\n};\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node);\n    case NodeTypes.TEXT:\n      return genText(node);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) => `${name}: \"${value?.content}\"`)\n    .join(\", \")}}, [${el.children.map((it) => genNode(it)).join(\", \")}])`;\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type ElementNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endToken = \"<\";\n\n  let endIndex = context.source.length;\n\n  const index = context.source.indexOf(endToken, 1);\n  if (index !== -1 && endIndex > index) {\n    endIndex = index;\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): AttributeNode[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(context: ParserContext, nameSet: Set<string>): AttributeNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  const loc = getSelection(context, start);\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/compiler-dom/index.ts",
    "content": "import { baseCompile } from \"../compiler-core\";\n\nexport function compile(template: string) {\n  return baseCompile(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n  }\n\n  // ------------------------ ここ\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render());\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/060_template_compiler\", () => {\n  it(\"should render template option\", () => {\n    const app = createApp({\n      template: `\n      <div class=\"container\" style=\"text-align: center\">\n        <h2>Hello, chibivue!</h2>\n        <img\n          width=\"150px\"\n          src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n          alt=\"Vue.js Logo\"\n        />\n        <p><b>chibivue</b> is the minimal Vue.js</p>\n  \n        <style>\n          .container {\n            height: 100vh;\n            padding: 16px;\n            background-color: #becdbe;\n            color: #2c3e50;\n          }\n        </style>\n      </div>\n    `,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      `<div class=\"container\" style=\"text-align: center\">\n        <h2>Hello, chibivue!</h2>\n        <img width=\"150px\" src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\" alt=\"Vue.js Logo\">\n        <p><b>chibivue</b> is the minimal Vue.js</p>\n  \n        <style>\n          .container {\n            height: 100vh;\n            padding: 16px;\n            background-color: #becdbe;\n            color: #2c3e50;\n          }\n        </style>\n      </div>`,\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler2/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/examples/playground/src/main.ts",
    "content": "import { createApp, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: \"Hello, chibivue!\", input: \"\" });\n\n    const changeMessage = () => {\n      state.message += \"!\";\n    };\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? \"\";\n    };\n\n    return { state, changeMessage, handleInput };\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport const generate = ({ children }: { children: TemplateChildNode[] }): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`;\n};\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it)).join(\", \")}])`;\n};\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/compiler-dom/index.ts",
    "content": "import { baseCompile } from \"../compiler-core\";\n\nexport function compile(template: string) {\n  return baseCompile(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"10_minimum_example/060_template_compiler3\", () => {\n  it(\"should render with template and mustache binding\", () => {\n    const state = reactive({ message: \"Hello chibivue!\" });\n    const app = createApp({\n      setup() {\n        return { state };\n      },\n      template: `<div>{{ state.message }}</div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>Hello chibivue!</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/060_template_compiler3/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/examples/playground/src/main.ts",
    "content": "import { createApp, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: \"Hello, chibivue!\", input: \"\" });\n\n    const changeMessage = () => {\n      state.message += \"!\";\n    };\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? \"\";\n    };\n\n    return { state, changeMessage, handleInput };\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport const generate = ({ children }: { children: TemplateChildNode[] }): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`;\n};\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it)).join(\", \")}])`;\n};\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/compiler-dom/index.ts",
    "content": "import { baseCompile } from \"../compiler-core\";\n\nexport function compile(template: string) {\n  return baseCompile(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/.vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"Vue.volar\", \"Vue.vscode-typescript-vue-plugin\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/README.md",
    "content": "# Vue 3 + TypeScript + Vite\n\nThis template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://vuejs.org/api/sfc-script-setup.html) to learn more.\n\n## Recommended IDE Setup\n\n- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar).\n\n## Type Support For `.vue` Imports in TS\n\nTypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, the [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) extension enables TypeScript support for `.vue` imports.\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + Vue + TS</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/package.json",
    "content": "{\n  \"name\": \"plugin-sample\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vue-tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"vue\": \"^3.2.47\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue\": \"^6.0.5\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\",\n    \"vue-tsc\": \"^2.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport HelloWorld from './components/HelloWorld.vue'\n</script>\n\n<template>\n  <div>\n    <a href=\"https://vitejs.dev\" target=\"_blank\">\n      <img src=\"/vite.svg\" class=\"logo\" alt=\"Vite logo\" />\n    </a>\n    <a href=\"https://vuejs.org/\" target=\"_blank\">\n      <img src=\"./assets/vue.svg\" class=\"logo vue\" alt=\"Vue logo\" />\n    </a>\n  </div>\n  <HelloWorld msg=\"Vite + Vue\" />\n</template>\n\n<style scoped>\n.logo {\n  height: 6em;\n  padding: 1.5em;\n  will-change: filter;\n  transition: filter 300ms;\n}\n.logo:hover {\n  filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.vue:hover {\n  filter: drop-shadow(0 0 2em #42b883aa);\n}\n</style>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/components/HelloWorld.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\ndefineProps<{ msg: string }>()\n\nconst count = ref(0)\n</script>\n\n<template>\n  <h1>{{ msg }}</h1>\n\n  <div class=\"card\">\n    <button type=\"button\" @click=\"count++\">count is {{ count }}</button>\n    <p>\n      Edit\n      <code>components/HelloWorld.vue</code> to test HMR\n    </p>\n  </div>\n\n  <p>\n    Check out\n    <a href=\"https://vuejs.org/guide/quick-start.html#local\" target=\"_blank\"\n      >create-vue</a\n    >, the official Vue + Vite starter\n  </p>\n  <p>\n    Install\n    <a href=\"https://github.com/vuejs/language-tools\" target=\"_blank\">Volar</a>\n    in your IDE for a better DX\n  </p>\n  <p class=\"read-the-docs\">Click on the Vite and Vue logos to learn more</p>\n</template>\n\n<style scoped>\n.read-the-docs {\n  color: #888;\n}\n</style>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/main.ts",
    "content": "import { createApp } from \"vue\";\nimport \"./style.css\";\nimport App from \"./App.vue\";\nimport \"./plugin.sample.js\";\n\ncreateApp(App).mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/plugin.sample.js",
    "content": "function fizzbuzz(n) {\n  for (let i = 1; i <= n; i++) {\n    i % 3 === 0 && i % 5 === 0\n      ? console.log(\"fizzbuzz\")\n      : i % 3 === 0\n        ? console.log(\"fizz\")\n        : i % 5 === 0\n          ? console.log(\"buzz\")\n          : console.log(i);\n  }\n}\n\nfizzbuzz(Math.floor(Math.random() * 100) + 1);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/style.css",
    "content": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #242424;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n}\n\na {\n  font-weight: 500;\n  color: #646cff;\n  text-decoration: inherit;\n}\na:hover {\n  color: #535bf2;\n}\n\nbody {\n  margin: 0;\n  display: flex;\n  place-items: center;\n  min-width: 320px;\n  min-height: 100vh;\n}\n\nh1 {\n  font-size: 3.2em;\n  line-height: 1.1;\n}\n\nbutton {\n  border-radius: 8px;\n  border: 1px solid transparent;\n  padding: 0.6em 1.2em;\n  font-size: 1em;\n  font-weight: 500;\n  font-family: inherit;\n  background-color: #1a1a1a;\n  cursor: pointer;\n  transition: border-color 0.25s;\n}\nbutton:hover {\n  border-color: #646cff;\n}\nbutton:focus,\nbutton:focus-visible {\n  outline: 4px auto -webkit-focus-ring-color;\n}\n\n.card {\n  padding: 2em;\n}\n\n#app {\n  max-width: 1280px;\n  margin: 0 auto;\n  padding: 2rem;\n  text-align: center;\n}\n\n@media (prefers-color-scheme: light) {\n  :root {\n    color: #213547;\n    background-color: #ffffff;\n  }\n  a:hover {\n    color: #747bff;\n  }\n  button {\n    background-color: #f9f9f9;\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"preserve\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.d.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/plugin-sample/vite.config.ts",
    "content": "import { type Plugin, defineConfig } from \"vite\";\nimport vue from \"@vitejs/plugin-vue\";\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [vue(), myPlugin()],\n});\n\nfunction myPlugin(): Plugin {\n  return {\n    name: \"vite:my-plugin\",\n\n    transform(code, id) {\n      if (id.endsWith(\".sample.js\")) {\n        let result = \"\";\n\n        for (let i = 0; i < 100; i++) {\n          result += `console.log(\"HelloWorld from plugin! (${i})\");\\n`;\n        }\n\n        result += code;\n\n        return { code: result };\n      }\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/tests/e2e.spec.ts",
    "content": "import { describe, expect, it } from \"vitest\";\n\nimport { compile } from \"../packages/compiler-dom\";\n\ndescribe(\"10_minimum_example/070_sfc_compiler\", () => {\n  it(\"should compile basic template to render function\", () => {\n    const template = `<div>Hello World</div>`;\n    const code = compile(template);\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain(\"h(\");\n    expect(code).toContain('\"div\"');\n    expect(code).toContain(\"Hello World\");\n  });\n\n  it(\"should compile template with nested elements\", () => {\n    const template = `<div><p>Nested content</p></div>`;\n    const code = compile(template);\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain('\"div\"');\n    expect(code).toContain('\"p\"');\n    expect(code).toContain(\"Nested content\");\n  });\n\n  it(\"should compile template with mustache interpolation\", () => {\n    const template = `<p>{{ message }}</p>`;\n    const code = compile(template);\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain(\"with (_ctx)\");\n    expect(code).toContain(\"message\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse } from \"../../compiler-sfc\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n\n    transform(code, id) {\n      if (!filter(id)) return;\n      const { descriptor } = parse(code, { filename: id });\n      console.log(\"🚀 ~ file: index.ts:14 ~ transform ~ descriptor:\", descriptor);\n      return { code: `export default {}` };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport const generate = ({ children }: { children: TemplateChildNode[] }): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`;\n};\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it)).join(\", \")}])`;\n};\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-dom/index.ts",
    "content": "import { baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string) {\n  return baseCompile(template);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/tests/e2e.spec.ts",
    "content": "import { describe, expect, it } from \"vitest\";\n\nimport { compile } from \"../packages/compiler-dom\";\nimport { parse } from \"../packages/compiler-sfc\";\n\ndescribe(\"10_minimum_example/070_sfc_compiler2\", () => {\n  it(\"should parse SFC with template, script, and style blocks\", () => {\n    const source = `\n<template>\n  <div>Hello</div>\n</template>\n\n<script>\nexport default {\n  setup() {\n    return {}\n  }\n}\n</script>\n\n<style>\ndiv { color: red; }\n</style>\n`.trim();\n\n    const { descriptor } = parse(source);\n\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain(\"<div>Hello</div>\");\n\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain(\"export default\");\n\n    expect(descriptor.styles.length).toBe(1);\n    expect(descriptor.styles[0].content).toContain(\"color: red\");\n  });\n\n  it(\"should compile template to render function code\", () => {\n    const template = `<div>Hello</div>`;\n    const code = compile(template);\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain(\"h(\");\n    expect(code).toContain('\"div\"');\n    expect(code).toContain(\"Hello\");\n  });\n\n  it(\"should compile template with interpolation\", () => {\n    const template = `<p>{{ message }}</p>`;\n    const code = compile(template);\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain(\"with (_ctx)\");\n    expect(code).toContain(\"message\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler2/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\\n\");\n\n      const { descriptor } = parse(code, { filename: id });\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/tests/e2e.spec.ts",
    "content": "import { describe, expect, it } from \"vitest\";\n\nimport { compile } from \"../packages/compiler-dom\";\nimport { parse } from \"../packages/compiler-sfc\";\n\ndescribe(\"10_minimum_example/070_sfc_compiler3\", () => {\n  it(\"should parse SFC with template and script blocks\", () => {\n    const source = `\n<template>\n  <div>{{ message }}</div>\n</template>\n\n<script>\nexport default {\n  setup() {\n    return { message: \"hello\" }\n  }\n}\n</script>\n`.trim();\n\n    const { descriptor } = parse(source);\n\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain(\"{{ message }}\");\n\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain('message: \"hello\"');\n  });\n\n  it(\"should compile template for non-browser mode\", () => {\n    const template = `<div>{{ count }}</div>`;\n    const code = compile(template, { isBrowser: false });\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain(\"_ctx.count\");\n    expect(code).not.toContain(\"with (_ctx)\");\n  });\n\n  it(\"should compile template for browser mode\", () => {\n    const template = `<div>{{ count }}</div>`;\n    const code = compile(template, { isBrowser: true });\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain(\"with (_ctx)\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler3/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { defineConfig } from \"vite\";\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountElement(n2, container);\n    } else {\n      patchElement(n1, n2);\n    }\n  };\n\n  const mountElement = (vnode: VNode, container: RendererElement) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (children: VNode[], container: RendererElement) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n\n    for (let i = 0; i < c2.length; i++) {\n      const child = (c2[i] = normalizeVNode(c2[i]));\n      patch(c1[i], child, container);\n    }\n  };\n\n  const processText = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    if (n1 == null) {\n      mountComponent(n2, container);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container);\n  };\n\n  return { render };\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/tests/e2e.spec.ts",
    "content": "import { describe, expect, it } from \"vitest\";\n\nimport { compile } from \"../packages/compiler-dom\";\nimport { parse, rewriteDefault } from \"../packages/compiler-sfc\";\n\ndescribe(\"10_minimum_example/070_sfc_compiler4\", () => {\n  it(\"should parse SFC with template, script, and style blocks\", () => {\n    const source = `\n<template>\n  <div class=\"container\">{{ message }}</div>\n</template>\n\n<script>\nexport default {\n  setup() {\n    return { message: \"hello\" }\n  }\n}\n</script>\n\n<style>\n.container { color: blue; }\n</style>\n`.trim();\n\n    const { descriptor } = parse(source);\n\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain('class=\"container\"');\n\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain(\"export default\");\n\n    expect(descriptor.styles.length).toBe(1);\n    expect(descriptor.styles[0].content).toContain(\".container\");\n  });\n\n  it(\"should rewrite default export\", () => {\n    const input = `export default { setup() { return {} } }`;\n    const result = rewriteDefault(input, \"_sfc_main\");\n\n    expect(result).toContain(\"const _sfc_main =\");\n    expect(result).not.toContain(\"export default\");\n  });\n\n  it(\"should handle missing default export\", () => {\n    const input = `const foo = 1;`;\n    const result = rewriteDefault(input, \"_sfc_main\");\n\n    expect(result).toContain(\"const _sfc_main = {}\");\n  });\n\n  it(\"should compile template with attributes\", () => {\n    const template = `<div class=\"box\" id=\"main\">content</div>`;\n    const code = compile(template, { isBrowser: false });\n\n    expect(code).toContain(\"function render\");\n    expect(code).toContain('\"div\"');\n    expect(code).toContain(\"class\");\n    expect(code).toContain(\"box\");\n  });\n\n  it(\"should compile nested elements\", () => {\n    const template = `<div><span>nested</span></div>`;\n    const code = compile(template, { isBrowser: false });\n\n    expect(code).toContain('\"div\"');\n    expect(code).toContain('\"span\"');\n    expect(code).toContain(\"nested\");\n  });\n});\n"
  },
  {
    "path": "book/impls/10_minimum_example/070_sfc_compiler4/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      list: [{ key: \"a\" }, { key: \"b\" }, { key: \"c\" }, { key: \"d\" }],\n    });\n    const updateList = () => {\n      state.list = [{ key: \"a\" }, { key: \"b\" }, { key: \"d\" }, { key: \"c\" }];\n    };\n\n    return () =>\n      h(\"div\", { id: \"app\" }, [\n        h(\n          \"ul\",\n          {},\n          state.list.map((item) => h(\"li\", { key: item.key }, [item.key])),\n        ),\n        h(\"button\", { onClick: updateList }, [\"update\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/baseHandler.ts",
    "content": "import { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (res !== null && typeof res === \"object\") {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (typeof type === \"string\") {\n      processElement(n1, n2, container, anchor);\n    } else if (typeof type === \"object\") {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1.children as VNode[];\n    const c2 = n2.children as VNode[];\n    patchKeyedChildren(c1, c2, container, anchor);\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, type } = vnode;\n    if (typeof type === \"object\") {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { type, children } = vnode;\n    if (typeof type === \"object\") {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-core/vnode.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n  };\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/shared/general.ts",
    "content": "const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"20_basic_virtual_dom/010_patch_keyed_children\", () => {\n  it(\"should patch keyed children correctly\", async () => {\n    const state = reactive({ list: [1, 2, 3] });\n    const app = createApp({\n      setup() {\n        return () =>\n          h(\n            \"ul\",\n            {},\n            state.list.map((item) => h(\"li\", { key: item }, [String(item)])),\n          );\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe('<ul><li key=\"1\">1</li><li key=\"2\">2</li><li key=\"3\">3</li></ul>');\n\n    state.list = [3, 2, 1];\n    await Promise.resolve();\n    expect(host.innerHTML).toBe('<ul><li key=\"3\">3</li><li key=\"2\">2</li><li key=\"1\">1</li></ul>');\n  });\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/010_patch_keyed_children/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      list: [{ key: \"a\" }, { key: \"b\" }, { key: \"c\" }, { key: \"d\" }],\n    });\n    const updateList = () => {\n      state.list = [{ key: \"a\" }, { key: \"b\" }, { key: \"d\" }, { key: \"c\" }];\n    };\n\n    return () =>\n      h(\"div\", { id: \"app\" }, [\n        h(\n          \"ul\",\n          {},\n          state.list.map((item) => h(\"li\", { key: item.key }, [item.key])),\n        ),\n        h(\"button\", { onClick: updateList }, [\"update\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      effect.run();\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));\n    const update = (instance.update = () => effect.run());\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-core/vnode.ts",
    "content": "import { isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (Array.isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/shared/general.ts",
    "content": "export const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"20_basic_virtual_dom/020_bit_flags\", () => {\n  it(\"should render and update with bit flags optimization\", async () => {\n    const state = reactive({ count: 0 });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${state.count}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    state.count++;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/020_bit_flags/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      message: \"Hello World\",\n    });\n    const updateList = () => {\n      state.message = \"Hello ChibiVue!\";\n      state.message = \"Hello ChibiVue!!\";\n      state.message = \"Hello ChibiVue!!\";\n      state.message = \"Hello ChibiVue!!\";\n      state.message = \"Hello ChibiVue!!\";\n      state.message = \"Hello ChibiVue!! last\";\n    };\n\n    return () => {\n      console.log(\"😎 rendered!\");\n      return h(\"div\", { id: \"app\" }, [\n        h(\"p\", {}, [`message: ${state.message}`]),\n        h(\"button\", { onClick: updateList }, [\"update\"]),\n      ]);\n    };\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      if (effect.scheduler) {\n        effect.scheduler();\n      } else {\n        effect.run();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-core/vnode.ts",
    "content": "import { isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (Array.isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/shared/general.ts",
    "content": "export const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"20_basic_virtual_dom/040_scheduler\", () => {\n  it(\"should batch multiple updates in scheduler\", async () => {\n    const state = reactive({ count: 0 });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${state.count}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    // Multiple synchronous updates should be batched\n    state.count++;\n    state.count++;\n    state.count++;\n\n    // DOM should not update synchronously\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    // Wait for scheduler to flush\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 3</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/040_scheduler/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/src/main.ts",
    "content": "import { createApp, h, nextTick, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    });\n    const updateState = async () => {\n      state.count++;\n      await nextTick();\n      const p = document.getElementById(\"count-p\");\n      if (p) {\n        console.log(\"😎 p.textContent\", p.textContent);\n      }\n    };\n\n    return () => {\n      return h(\"div\", { id: \"app\" }, [\n        h(\"p\", { id: \"count-p\" }, [`${state.count}`]),\n        h(\"button\", { onClick: updateState }, [\"update\"]),\n      ]);\n    };\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      if (effect.scheduler) {\n        effect.scheduler();\n      } else {\n        effect.run();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, value: any): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const props = n2.props;\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in props) {\n      if (props[key] !== n1.props?.[key]) {\n        hostPatchProp(el, key, props[key]);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-core/vnode.ts",
    "content": "import { isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (Array.isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value);\n  } else {\n    patchAttr(el, key, value);\n  }\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/shared/general.ts",
    "content": "export const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, nextTick, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"20_basic_virtual_dom/050_next_tick\", () => {\n  it(\"should update DOM after nextTick\", async () => {\n    const state = reactive({ count: 0 });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${state.count}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    state.count++;\n    await nextTick();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/050_next_tick/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/examples/playground/src/main.ts",
    "content": "import { createApp, h, nextTick, reactive } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    });\n    const updateState = async () => {\n      state.count++;\n      await nextTick();\n      const p = document.getElementById(\"count-p\");\n      if (p) {\n        console.log(\"😎 p.textContent\", p.textContent);\n      }\n    };\n\n    return () => {\n      return h(\"div\", { id: \"app\" }, [\n        h(\"p\", { id: \"count-p\" }, [`${state.count}`]),\n        h(\"button\", { onClick: updateState }, [\"update\"]),\n      ]);\n    };\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/effect.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    for (const effect of effects) {\n      if (effect.scheduler) {\n        effect.scheduler();\n      } else {\n        effect.run();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/index.ts",
    "content": "export { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/reactivity/reactive.ts",
    "content": "import { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"20_basic_virtual_dom/060_other_props\", () => {\n  describe(\"class\", () => {\n    it(\"should handle class prop\", async () => {\n      const state = reactive({ active: false });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { class: state.active ? \"active\" : \"\" }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.className).toBe(\"\");\n\n      state.active = true;\n      await Promise.resolve();\n      expect(div.className).toBe(\"active\");\n    });\n\n    it(\"should remove class when set to null\", async () => {\n      const state = reactive<{ cls: string | null }>({ cls: \"foo\" });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { class: state.cls }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.className).toBe(\"foo\");\n\n      state.cls = null;\n      await Promise.resolve();\n      expect(div.hasAttribute(\"class\")).toBe(false);\n    });\n  });\n\n  describe(\"style\", () => {\n    it(\"should handle style object\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { style: { color: \"red\", fontSize: \"14px\" } }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.style.color).toBe(\"red\");\n      expect(div.style.fontSize).toBe(\"14px\");\n    });\n\n    it(\"should handle style string\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { style: \"color: blue; font-size: 16px\" }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.style.color).toBe(\"blue\");\n      expect(div.style.fontSize).toBe(\"16px\");\n    });\n\n    it(\"should update style\", async () => {\n      const state = reactive({ color: \"red\" });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { style: { color: state.color } }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.style.color).toBe(\"red\");\n\n      state.color = \"blue\";\n      await Promise.resolve();\n      expect(div.style.color).toBe(\"blue\");\n    });\n\n    it(\"should remove old style properties\", async () => {\n      const state = reactive<{ style: Record<string, string> }>({\n        style: { color: \"red\", fontSize: \"14px\" },\n      });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { style: state.style }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.style.color).toBe(\"red\");\n      expect(div.style.fontSize).toBe(\"14px\");\n\n      state.style = { color: \"blue\" };\n      await Promise.resolve();\n      expect(div.style.color).toBe(\"blue\");\n      expect(div.style.fontSize).toBe(\"\");\n    });\n\n    it(\"should handle CSS custom properties\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { style: { \"--custom-color\": \"red\" } }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.style.getPropertyValue(\"--custom-color\")).toBe(\"red\");\n    });\n\n    it(\"should remove style when set to null\", async () => {\n      const state = reactive<{ style: Record<string, string> | null }>({\n        style: { color: \"red\" },\n      });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { style: state.style }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.style.color).toBe(\"red\");\n\n      state.style = null;\n      await Promise.resolve();\n      expect(div.style.color).toBe(\"\");\n    });\n  });\n\n  describe(\"attrs\", () => {\n    it(\"should set attributes\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { \"data-id\": \"123\", \"aria-label\": \"test\" }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.getAttribute(\"data-id\")).toBe(\"123\");\n      expect(div.getAttribute(\"aria-label\")).toBe(\"test\");\n    });\n\n    it(\"should update attributes\", async () => {\n      const state = reactive({ id: \"foo\" });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { \"data-id\": state.id }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.getAttribute(\"data-id\")).toBe(\"foo\");\n\n      state.id = \"bar\";\n      await Promise.resolve();\n      expect(div.getAttribute(\"data-id\")).toBe(\"bar\");\n    });\n\n    it(\"should remove attributes when set to null\", async () => {\n      const state = reactive<{ id: string | null }>({ id: \"foo\" });\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { \"data-id\": state.id }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.getAttribute(\"data-id\")).toBe(\"foo\");\n\n      state.id = null;\n      await Promise.resolve();\n      expect(div.hasAttribute(\"data-id\")).toBe(false);\n    });\n\n    it(\"should treat spellcheck as attribute\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"input\", { spellcheck: \"false\" }, []);\n        },\n      });\n      app.mount(\"#host\");\n\n      const input = host.querySelector(\"input\") as HTMLInputElement;\n      expect(input.getAttribute(\"spellcheck\")).toBe(\"false\");\n    });\n\n    it(\"should treat draggable as attribute\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { draggable: \"true\" }, [\"test\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.getAttribute(\"draggable\")).toBe(\"true\");\n    });\n  });\n\n  describe(\"DOM props\", () => {\n    it(\"should set innerHTML\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { innerHTML: \"<span>hello</span>\" }, []);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.innerHTML).toBe(\"<span>hello</span>\");\n    });\n\n    it(\"should set textContent\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"div\", { textContent: \"hello world\" }, []);\n        },\n      });\n      app.mount(\"#host\");\n\n      const div = host.querySelector(\"div\") as HTMLDivElement;\n      expect(div.textContent).toBe(\"hello world\");\n    });\n\n    it(\"should set value on input\", async () => {\n      const state = reactive({ value: \"initial\" });\n      const app = createApp({\n        setup() {\n          return () => h(\"input\", { value: state.value }, []);\n        },\n      });\n      app.mount(\"#host\");\n\n      const input = host.querySelector(\"input\") as HTMLInputElement;\n      expect(input.value).toBe(\"initial\");\n\n      state.value = \"updated\";\n      await Promise.resolve();\n      expect(input.value).toBe(\"updated\");\n    });\n\n    it(\"should set checked on checkbox\", async () => {\n      const state = reactive({ checked: false });\n      const app = createApp({\n        setup() {\n          return () => h(\"input\", { type: \"checkbox\", checked: state.checked }, []);\n        },\n      });\n      app.mount(\"#host\");\n\n      const input = host.querySelector(\"input\") as HTMLInputElement;\n      expect(input.checked).toBe(false);\n\n      state.checked = true;\n      await Promise.resolve();\n      expect(input.checked).toBe(true);\n    });\n\n    it(\"should handle boolean attributes correctly\", () => {\n      const app = createApp({\n        setup() {\n          return () => h(\"select\", { multiple: \"\" }, []);\n        },\n      });\n      app.mount(\"#host\");\n\n      const select = host.querySelector(\"select\") as HTMLSelectElement;\n      expect(select.multiple).toBe(true);\n    });\n  });\n\n  describe(\"events\", () => {\n    it(\"should handle click event\", async () => {\n      const onClick = vi.fn();\n      const app = createApp({\n        setup() {\n          return () => h(\"button\", { onClick }, [\"click me\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const button = host.querySelector(\"button\") as HTMLButtonElement;\n      button.click();\n      expect(onClick).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should update event handler\", async () => {\n      const handler1 = vi.fn();\n      const handler2 = vi.fn();\n      const state = reactive({ handler: handler1 });\n      const app = createApp({\n        setup() {\n          return () => h(\"button\", { onClick: state.handler }, [\"click me\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const button = host.querySelector(\"button\") as HTMLButtonElement;\n      button.click();\n      expect(handler1).toHaveBeenCalledTimes(1);\n      expect(handler2).toHaveBeenCalledTimes(0);\n\n      state.handler = handler2;\n      await Promise.resolve();\n      button.click();\n      expect(handler1).toHaveBeenCalledTimes(1);\n      expect(handler2).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should remove event handler when set to null\", async () => {\n      const onClick = vi.fn();\n      const state = reactive<{ handler: (() => void) | null }>({ handler: onClick });\n      const app = createApp({\n        setup() {\n          return () => h(\"button\", { onClick: state.handler }, [\"click me\"]);\n        },\n      });\n      app.mount(\"#host\");\n\n      const button = host.querySelector(\"button\") as HTMLButtonElement;\n      button.click();\n      expect(onClick).toHaveBeenCalledTimes(1);\n\n      state.handler = null;\n      await Promise.resolve();\n      button.click();\n      expect(onClick).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should pass event object to handler\", () => {\n      let receivedEvent: Event | null = null;\n      const app = createApp({\n        setup() {\n          return () =>\n            h(\n              \"button\",\n              {\n                onClick: (e: Event) => {\n                  receivedEvent = e;\n                },\n              },\n              [\"click me\"],\n            );\n        },\n      });\n      app.mount(\"#host\");\n\n      const button = host.querySelector(\"button\") as HTMLButtonElement;\n      button.click();\n      expect(receivedEvent).toBeInstanceOf(Event);\n    });\n  });\n});\n"
  },
  {
    "path": "book/impls/20_basic_virtual_dom/060_other_props/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const message = ref('Hello, chibivue!')\n\n    const increment = () => {\n      count.value++\n    }\n\n    const updateMessage = () => {\n      message.value = `Count is now ${count.value}`\n    }\n\n    return { count, message, increment, updateMessage }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>ref Example</h2>\n\n    <p>Count: {{ count }}</p>\n    <p>Message: {{ message }}</p>\n\n    <button @click=\"increment\">Increment</button>\n    <button @click=\"updateMessage\">Update Message</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/src/main.ts",
    "content": "import { createApp, h, ref } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const count = ref(0);\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${count.value}`]),\n        h(\"button\", { onClick: () => count.value++ }, [\"Increment\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(\".\" + filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/reactivity/index.ts",
    "content": "export { ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/reactivity/ref.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\nimport { trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value);\n}\n\nfunction createRef(rawValue: unknown) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(value: T) {\n    this._value = toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/010_ref\", () => {\n  it(\"should render with ref\", async () => {\n    const count = ref(0);\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${count.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    count.value++;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/010_ref/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, shallowRef } from 'chibivue'\n\nexport default {\n  setup() {\n    const deepRef = ref({ nested: { count: 0 } })\n    const shallow = shallowRef({ nested: { count: 0 } })\n\n    const updateDeep = () => {\n      deepRef.value.nested.count++\n    }\n\n    const updateShallow = () => {\n      // This won't trigger re-render\n      shallow.value.nested.count++\n    }\n\n    const replaceShallow = () => {\n      // This will trigger re-render\n      shallow.value = { nested: { count: shallow.value.nested.count + 1 } }\n    }\n\n    return { deepRef, shallow, updateDeep, updateShallow, replaceShallow }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>shallowRef Example</h2>\n\n    <div>\n      <h3>Deep ref</h3>\n      <p>Count: {{ deepRef.nested.count }}</p>\n      <button @click=\"updateDeep\">Update nested (triggers)</button>\n    </div>\n\n    <div>\n      <h3>Shallow ref</h3>\n      <p>Count: {{ shallow.nested.count }}</p>\n      <button @click=\"updateShallow\">Update nested (no trigger)</button>\n      <button @click=\"replaceShallow\">Replace value (triggers)</button>\n    </div>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/src/main.ts",
    "content": "import { createApp, h, shallowRef, triggerRef } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 });\n    const forceUpdate = () => {\n      triggerRef(state);\n    };\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${state.value.count}`]),\n\n        h(\n          \"button\",\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 };\n            },\n          },\n          [\"increment\"],\n        ),\n\n        h(\n          \"button\", // clickしても描画は更新されない\n          {\n            onClick: () => {\n              state.value.count++;\n            },\n          },\n          [\"not trigger ...\"],\n        ),\n\n        h(\n          \"button\", // 描画が今の state.value.count が持つ値に更新される\n          { onClick: forceUpdate },\n          [\"force update !\"],\n        ),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/reactivity/ref.ts",
    "content": "import { type Dep, createDep } from \"./dep\";\nimport { trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/shared/index.ts",
    "content": "export * from \"./general\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, shallowRef, triggerRef } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/020_shallow_ref\", () => {\n  it(\"should not trigger updates for deep changes with shallowRef\", async () => {\n    const state = shallowRef({ count: 0 });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${state.value.count}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    // Deep change should not trigger update\n    state.value.count = 1;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    // triggerRef should force update\n    triggerRef(state);\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/020_shallow_ref/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive, toRef } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({\n      name: 'chibivue',\n      count: 0,\n    })\n\n    // Create ref that syncs with reactive property\n    const nameRef = toRef(state, 'name')\n    const countRef = toRef(state, 'count')\n\n    const updateName = () => {\n      nameRef.value = 'updated chibivue'\n    }\n\n    const incrementCount = () => {\n      countRef.value++\n    }\n\n    return { state, nameRef, countRef, updateName, incrementCount }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>toRef Example</h2>\n\n    <h3>From reactive object</h3>\n    <p>state.name: {{ state.name }}</p>\n    <p>state.count: {{ state.count }}</p>\n\n    <h3>Via toRef</h3>\n    <p>nameRef: {{ nameRef }}</p>\n    <p>countRef: {{ countRef }}</p>\n\n    <button @click=\"updateName\">Update via nameRef</button>\n    <button @click=\"incrementCount\">Increment via countRef</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, toRef } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n    const stateCountRef = toRef(state, \"count\");\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`state.count: ${state.count}`]),\n        h(\"p\", {}, [`stateCountRef.value: ${stateCountRef.value}`]),\n        h(\"button\", { onClick: () => state.count++ }, [\"updateState\"]),\n        h(\"button\", { onClick: () => stateCountRef.value++ }, [\"updateRef\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n    ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/reactivity/ref.ts",
    "content": "import type { IfAny } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive, toRef } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/030_to_ref\", () => {\n  it(\"should create ref from reactive property\", async () => {\n    const state = reactive({ count: 0 });\n    const countRef = toRef(state, \"count\");\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${countRef.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    state.count++;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/030_to_ref/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive, toRefs } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({\n      name: 'chibivue',\n      count: 0,\n      version: '1.0.0',\n    })\n\n    // Convert all properties to refs\n    const { name, count, version } = toRefs(state)\n\n    const updateName = () => {\n      name.value = 'updated chibivue'\n    }\n\n    const incrementCount = () => {\n      count.value++\n    }\n\n    return { state, name, count, version, updateName, incrementCount }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>toRefs Example</h2>\n\n    <h3>From reactive object</h3>\n    <p>state.name: {{ state.name }}</p>\n    <p>state.count: {{ state.count }}</p>\n    <p>state.version: {{ state.version }}</p>\n\n    <h3>Via destructured toRefs</h3>\n    <p>name: {{ name }}</p>\n    <p>count: {{ count }}</p>\n    <p>version: {{ version }}</p>\n\n    <button @click=\"updateName\">Update name</button>\n    <button @click=\"incrementCount\">Increment count</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, toRefs } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ foo: 1, bar: 2 });\n    const stateAsRefs = toRefs(state);\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`[state]: foo: ${state.foo}, bar: ${state.bar}`]),\n        h(\"p\", {}, [`[stateAsRefs]: foo: ${stateAsRefs.foo.value}, bar: ${stateAsRefs.bar.value}`]),\n        h(\"button\", { onClick: () => state.foo++ }, [\"update state.foo\"]),\n        h(\"button\", { onClick: () => stateAsRefs.bar.value++ }, [\"update stateAsRefs.bar.value\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive, toRefs } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/040_to_refs\", () => {\n  it(\"should create refs from reactive object\", async () => {\n    const state = reactive({ count: 0, name: \"test\" });\n    const { count, name } = toRefs(state);\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`${name.value}: ${count.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>test: 0</div>\");\n\n    state.count++;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>test: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/040_to_refs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, computed } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const price = ref(100)\n\n    // Computed property\n    const doubleCount = computed(() => count.value * 2)\n    const total = computed(() => count.value * price.value)\n\n    const increment = () => count.value++\n    const decrement = () => count.value--\n    const increasePrice = () => (price.value += 50)\n\n    return { count, price, doubleCount, total, increment, decrement, increasePrice }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>computed Example</h2>\n\n    <div>\n      <p>Count: {{ count }}</p>\n      <p>Price: {{ price }}</p>\n    </div>\n\n    <div>\n      <p>Double count (computed): {{ doubleCount }}</p>\n      <p>Total (count * price): {{ total }}</p>\n    </div>\n\n    <button @click=\"increment\">Increment</button>\n    <button @click=\"decrement\">Decrement</button>\n    <button @click=\"increasePrice\">Increase Price</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/src/main.ts",
    "content": "import { computed, createApp, h, reactive, ref } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const count = reactive({ value: 0 });\n    const count2 = reactive({ value: 0 });\n    const double = computed(() => {\n      console.log(\"computed\");\n      return count.value * 2;\n    });\n    const doubleDouble = computed(() => {\n      console.log(\"computed (doubleDouble)\");\n      return double.value * 2;\n    });\n\n    const countRef = ref(0);\n    const doubleCountRef = computed(() => {\n      console.log(\"computed (doubleCountRef)\");\n      return countRef.value * 2;\n    });\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${count.value}`]),\n        h(\"p\", {}, [`count2: ${count2.value}`]),\n        h(\"p\", {}, [`double: ${double.value}`]),\n        h(\"p\", {}, [`doubleDouble: ${doubleDouble.value}`]),\n        h(\"p\", {}, [`doubleCountRef: ${doubleCountRef.value}`]),\n        h(\"button\", { onClick: () => count.value++ }, [\"update count\"]),\n        h(\"button\", { onClick: () => count2.value++ }, [\"update count2\"]),\n        h(\"button\", { onClick: () => countRef.value++ }, [\"update countRef\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/computed.ts",
    "content": "import type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\ntype ComputedGetter<T> = (...args: any[]) => T;\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(getter: ComputedGetter<T>) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(_newValue: T) {}\n}\n\nexport function computed<T>(getter: ComputedGetter<T>): ComputedRef<T> {\n  return new ComputedRefImpl(getter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport { computed } from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { computed, createApp, h, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/050_computed\", () => {\n  it(\"should compute derived values\", async () => {\n    const count = ref(0);\n    const doubled = computed(() => count.value * 2);\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`doubled: ${doubled.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>doubled: 0</div>\");\n\n    count.value = 5;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>doubled: 10</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/050_computed/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, computed } from 'chibivue'\n\nexport default {\n  setup() {\n    const firstName = ref('Vue')\n    const lastName = ref('chibivue')\n\n    // Computed with getter and setter\n    const fullName = computed({\n      get: () => `${firstName.value} ${lastName.value}`,\n      set: (val) => {\n        const parts = val.split(' ')\n        firstName.value = parts[0] || ''\n        lastName.value = parts[1] || ''\n      },\n    })\n\n    const updateFullName = () => {\n      fullName.value = 'Hello World'\n    }\n\n    return { firstName, lastName, fullName, updateFullName }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>computed (with setter) Example</h2>\n\n    <div>\n      <label>\n        First Name:\n        <input :value=\"firstName\" @input=\"firstName = $event.target.value\" />\n      </label>\n    </div>\n\n    <div>\n      <label>\n        Last Name:\n        <input :value=\"lastName\" @input=\"lastName = $event.target.value\" />\n      </label>\n    </div>\n\n    <p>Full Name (computed): {{ fullName }}</p>\n\n    <button @click=\"updateFullName\">Set fullName = \"Hello World\"</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nlabel {\n  display: block;\n  margin: 8px 0;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/src/main.ts",
    "content": "import { computed, createApp, h, ref } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const count = ref(0);\n    const writeableDouble = computed<number>({\n      get: () => count.value * 2,\n      set: (val) => void (count.value = val),\n    });\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${count.value}`]),\n        h(\"p\", {}, [`double: ${writeableDouble.value}`]),\n        h(\"button\", { onClick: () => count.value++ }, [\"update count\"]),\n        h(\"button\", { onClick: () => (writeableDouble.value += 1) }, [\"update double\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/baseHandler.ts",
    "content": "import { isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n\nconst hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { computed, createApp, h, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/060_computed_setter\", () => {\n  it(\"should support computed setter\", async () => {\n    const count = ref(0);\n    const doubled = computed({\n      get: () => count.value * 2,\n      set: (val) => {\n        count.value = val / 2;\n      },\n    });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${count.value}, doubled: ${doubled.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0, doubled: 0</div>\");\n\n    doubled.value = 10;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 5, doubled: 10</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/060_computed_setter/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, watch } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const name = ref('chibivue')\n    const logs = ref([])\n\n    // Watch single ref\n    watch(count, (newVal, oldVal) => {\n      logs.value.push(`count changed: ${oldVal} -> ${newVal}`)\n    })\n\n    // Watch another ref\n    watch(name, (newVal, oldVal) => {\n      logs.value.push(`name changed: \"${oldVal}\" -> \"${newVal}\"`)\n    })\n\n    const increment = () => count.value++\n    const updateName = () => (name.value = name.value + '!')\n\n    return { count, name, logs, increment, updateName }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>watch Example</h2>\n\n    <div>\n      <p>Count: {{ count }}</p>\n      <button @click=\"increment\">Increment</button>\n    </div>\n\n    <div>\n      <p>Name: {{ name }}</p>\n      <button @click=\"updateName\">Add !</button>\n    </div>\n\n    <h3>Watch Logs:</h3>\n    <ul>\n      <li v-for=\"(log, i) in logs\" :key=\"i\">{{ log }}</li>\n    </ul>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, watch } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n    watch(\n      () => state.count,\n      () => alert(\"state.count was changed!\"),\n    );\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\"button\", { onClick: () => state.count++ }, [\"update state\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/apiWatch.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { hasChanged } from \"../shared\";\n\nexport type WatchEffect = (onCleanup: OnCleanup) => void;\n\nexport type WatchSource<T = any> = () => T;\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(source: WatchSource<T>, cb: (newValue: T, oldValue: T) => void) {\n  const getter = () => source();\n  let oldValue = getter();\n  const job = () => {\n    const newValue = getter();\n    if (hasChanged(newValue, oldValue)) {\n      cb(newValue, oldValue);\n      oldValue = newValue;\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n  effect.run();\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/tests/e2e.spec.ts",
    "content": "import { describe, expect, it, vi } from \"vitest\";\n\nimport { ref, watch } from \"../packages\";\n\ndescribe(\"30_basic_reactivity_system/070_watch\", () => {\n  it(\"should watch getter function\", async () => {\n    const count = ref(0);\n    const callback = vi.fn();\n\n    // At this stage, watch only accepts getter function\n    watch(() => count.value, callback);\n\n    count.value = 1;\n    await Promise.resolve();\n    expect(callback).toHaveBeenCalledWith(1, 0);\n\n    count.value = 2;\n    await Promise.resolve();\n    expect(callback).toHaveBeenCalledWith(2, 1);\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/070_watch/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, watch } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const message = ref('')\n    const logs = ref([])\n\n    // Watch with immediate option\n    watch(\n      count,\n      (newVal, oldVal) => {\n        logs.value.push(`[immediate] count changed: ${oldVal} -> ${newVal}`)\n      },\n      { immediate: true }\n    )\n\n    // Watch with deep option\n    const obj = ref({ nested: { value: 0 } })\n    watch(\n      obj,\n      (newVal) => {\n        logs.value.push(`[deep] nested.value = ${newVal.nested.value}`)\n      },\n      { deep: true }\n    )\n\n    const increment = () => count.value++\n    const updateNested = () => obj.value.nested.value++\n\n    return { count, message, logs, obj, increment, updateNested }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>watch API Extensions Example</h2>\n\n    <div>\n      <p>Count: {{ count }}</p>\n      <button @click=\"increment\">Increment (immediate watch)</button>\n    </div>\n\n    <div>\n      <p>Nested value: {{ obj.nested.value }}</p>\n      <button @click=\"updateNested\">Update nested (deep watch)</button>\n    </div>\n\n    <h3>Logs:</h3>\n    <ul>\n      <li v-for=\"(log, i) in logs\" :key=\"i\">{{ log }}</li>\n    </ul>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, ref, watch } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n    watch(\n      () => state.count,\n      (crr, prev) => alert(`state.count was changed! ${prev} -> ${crr}`),\n    );\n\n    const count = ref(0);\n    watch(count, (crr, prev) => alert(`count.value was changed! ${prev} -> ${crr}`));\n\n    const count2 = ref(0);\n    watch([count, count2], (crr, prev) =>\n      alert(`count.value or count2.value was changed! ${prev} -> ${crr}`),\n    );\n\n    watch(count, () => alert(\"immediate watcher\"), { immediate: true });\n\n    watch(\n      () => state,\n      () => alert(\"deep watcher\"),\n      { deep: true },\n    );\n\n    watch(state, () => alert(\"deep watcher (auto)\"));\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`state.count: ${state.count}`]),\n        h(\"button\", { onClick: () => state.count++ }, [\"update state\"]),\n\n        h(\"p\", {}, [`count: ${count.value}`]),\n        h(\"button\", { onClick: () => count.value++ }, [\"update count\"]),\n\n        h(\"p\", {}, [`count2: ${count2.value}`]),\n        h(\"button\", { onClick: () => count2.value++ }, [\"update count2\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = (onCleanup: OnCleanup) => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T> | object;\n\nexport type WatchCallback<V = any, OV = any> = (value: V, oldValue: OV) => void;\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef<T>(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let oldValue: T | T[];\n  const job = () => {\n    const newValue = effect.run();\n    if (\n      option.deep ||\n      (isMultiSource\n        ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as T[])?.[i]))\n        : hasChanged(newValue, oldValue))\n    ) {\n      cb(newValue, oldValue);\n      oldValue = newValue;\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/shared/general.ts",
    "content": "export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/tests/e2e.spec.ts",
    "content": "import { describe, expect, it, vi } from \"vitest\";\n\nimport { reactive, watch } from \"../packages\";\n\ndescribe(\"30_basic_reactivity_system/080_watch_api_extends\", () => {\n  it(\"should watch reactive object with deep option\", async () => {\n    const state = reactive({ nested: { count: 0 } });\n    const callback = vi.fn();\n\n    watch(state, callback, { deep: true });\n\n    state.nested.count = 1;\n    await Promise.resolve();\n    expect(callback).toHaveBeenCalled();\n  });\n\n  it(\"should call immediately with immediate option\", async () => {\n    const state = reactive({ count: 0 });\n    const callback = vi.fn();\n\n    watch(() => state.count, callback, { immediate: true });\n\n    // At this stage, onCleanup is not passed to callback\n    expect(callback).toHaveBeenCalledWith(0, undefined);\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/080_watch_api_extends/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, watchEffect } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const name = ref('chibivue')\n    const logs = ref([])\n\n    // watchEffect automatically tracks dependencies\n    watchEffect(() => {\n      logs.value.push(`watchEffect: count=${count.value}, name=${name.value}`)\n    })\n\n    const increment = () => count.value++\n    const updateName = () => (name.value = name.value + '!')\n\n    return { count, name, logs, increment, updateName }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>watchEffect Example</h2>\n\n    <div>\n      <p>Count: {{ count }}</p>\n      <button @click=\"increment\">Increment</button>\n    </div>\n\n    <div>\n      <p>Name: {{ name }}</p>\n      <button @click=\"updateName\">Add !</button>\n    </div>\n\n    <h3>Logs (watchEffect auto-tracks both):</h3>\n    <ul>\n      <li v-for=\"(log, i) in logs\" :key=\"i\">{{ log }}</li>\n    </ul>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/src/main.ts",
    "content": "import { createApp, h, ref, watchEffect } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const count = ref(0);\n\n    watchEffect(() => console.log(count.value));\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${count.value}`]),\n        h(\"button\", { onClick: () => count.value++ }, [\"update count\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/reactive.ts",
    "content": "import { isObject } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (value: V, oldValue: OV) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        cb(newValue, oldValue);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/shared/general.ts",
    "content": "export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isArray = Array.isArray;\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/tests/e2e.spec.ts",
    "content": "import { describe, expect, it, vi } from \"vitest\";\n\nimport { ref, watchEffect } from \"../packages\";\n\ndescribe(\"30_basic_reactivity_system/090_watch_effect\", () => {\n  it(\"should run effect immediately and on changes\", async () => {\n    const count = ref(0);\n    const callback = vi.fn();\n\n    watchEffect(() => {\n      callback(count.value);\n    });\n\n    // Should run immediately\n    expect(callback).toHaveBeenCalledWith(0);\n\n    count.value = 1;\n    await Promise.resolve();\n    expect(callback).toHaveBeenCalledWith(1);\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/090_watch_effect/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive, ref } from 'chibivue'\n\nexport default {\n  setup() {\n    // Object\n    const obj = reactive({ count: 0, name: 'chibivue' })\n\n    // Array\n    const arr = reactive([1, 2, 3])\n\n    // Map\n    const map = reactive(new Map([['key1', 'value1']]))\n\n    // Set\n    const set = reactive(new Set([1, 2, 3]))\n\n    const updateObj = () => obj.count++\n    const pushArr = () => arr.push(arr.length + 1)\n    const setMap = () => map.set(`key${map.size + 1}`, `value${map.size + 1}`)\n    const addSet = () => set.add(set.size + 1)\n\n    return { obj, arr, map, set, updateObj, pushArr, setMap, addSet }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Reactive Proxy Target Types Example</h2>\n\n    <div>\n      <h3>Object</h3>\n      <p>{{ obj }}</p>\n      <button @click=\"updateObj\">Increment count</button>\n    </div>\n\n    <div>\n      <h3>Array</h3>\n      <p>{{ arr }}</p>\n      <button @click=\"pushArr\">Push</button>\n    </div>\n\n    <div>\n      <h3>Map</h3>\n      <p>Size: {{ map.size }}</p>\n      <button @click=\"setMap\">Set new entry</button>\n    </div>\n\n    <div>\n      <h3>Set</h3>\n      <p>Size: {{ set.size }}</p>\n      <button @click=\"addSet\">Add new value</button>\n    </div>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/src/main.ts",
    "content": "import { createApp, h, ref } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null);\n    const getRef = () => {\n      inputRef.value = document.getElementById(\"my-input\") as HTMLInputElement | null;\n    };\n    const focus = () => {\n      inputRef.value?.focus();\n    };\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"input\", { id: \"my-input\" }, []),\n        h(\"button\", { onClick: getRef }, [\"getRef\"]),\n        h(\"button\", { onClick: focus }, [\"focus\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport function reactive<T extends object>(target: T): T {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (value: V, oldValue: OV) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        cb(newValue, oldValue);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-core/vnode.ts",
    "content": "import { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/100_reactive_proxy_target_type\", () => {\n  it(\"should return original for invalid target types (Map/Set)\", () => {\n    // At this stage, Map and Set are not yet supported and return original\n    const map = new Map([[\"count\", 0]]);\n    const reactiveMap = reactive(map);\n    expect(reactiveMap).toBe(map);\n\n    const set = new Set([1, 2, 3]);\n    const reactiveSet = reactive(set);\n    expect(reactiveSet).toBe(set);\n  });\n\n  it(\"should handle Object reactively\", async () => {\n    const state = reactive({ count: 0 });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${state.count}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    state.count = 1;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n\n  it(\"should handle Array reactively\", async () => {\n    const state = reactive({ items: [1, 2, 3] });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [state.items.join(\",\")]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>1,2,3</div>\");\n\n    // Direct assignment works at this stage\n    state.items = [1, 2, 3, 4];\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>1,2,3,4</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/100_reactive_proxy_target_type/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref, onMounted } from 'chibivue'\n\nexport default {\n  setup() {\n    // Template ref\n    const inputRef = ref(null)\n    const divRef = ref(null)\n\n    const focusInput = () => {\n      inputRef.value?.focus()\n    }\n\n    const logDivContent = () => {\n      if (divRef.value) {\n        console.log('Div content:', divRef.value.textContent)\n        alert('Check console for div content!')\n      }\n    }\n\n    onMounted(() => {\n      console.log('Input element:', inputRef.value)\n      console.log('Div element:', divRef.value)\n    })\n\n    return { inputRef, divRef, focusInput, logDivContent }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Template Refs Example</h2>\n\n    <div>\n      <input ref=\"inputRef\" placeholder=\"Focus me with the button\" />\n      <button @click=\"focusInput\">Focus Input</button>\n    </div>\n\n    <div ref=\"divRef\" class=\"content-box\">\n      This is a div with a template ref. Click the button to log its content.\n    </div>\n    <button @click=\"logDivContent\">Log Div Content</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n.content-box {\n  margin: 16px 0;\n  padding: 16px;\n  background-color: #f0f0f0;\n  border-radius: 4px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/src/main.ts",
    "content": "import { createApp, h, ref } from \"chibivue\";\n\nconst Child = {\n  setup() {\n    const action = () => alert(\"clicked!\");\n    return { action };\n  },\n\n  template: `<button @click=\"action\">action (child)</button>`,\n};\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null);\n    const focus = () => {\n      inputRef.value?.focus();\n    };\n\n    const childRef = ref<any>(null);\n    const childAction = () => {\n      childRef.value?.action();\n    };\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"input\", { ref: inputRef }, []),\n        h(\"button\", { onClick: focus }, [\"focus\"]),\n        h(\"hr\", {}, []),\n        h(\"div\", {}, [\n          h(Child, { ref: childRef }, []),\n          h(\"button\", { onClick: childAction }, [\"action (parent)\"]),\n        ]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isObject } from \"../shared\";\nimport { track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/effect.ts",
    "content": "import { isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  const dep = depsMap.get(key);\n\n  if (dep) {\n    const effects = [...dep];\n    triggerEffects(effects);\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport function reactive<T extends object>(target: T): T {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(target, mutableHandlers);\n  return proxy as T;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (value: V, oldValue: OV) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        cb(newValue, oldValue);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/110_template_refs\", () => {\n  it(\"should set template ref\", async () => {\n    const divRef = ref<HTMLDivElement | null>(null);\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", { ref: divRef }, [\"test\"]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(divRef.value).toBeInstanceOf(HTMLDivElement);\n    expect(divRef.value?.textContent).toBe(\"test\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/110_template_refs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive, ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({\n      count: 0,\n      items: [1, 2, 3],\n      nested: { value: 'hello' },\n    })\n\n    // Demonstrate various proxy operations\n    const incrementCount = () => state.count++\n    const pushItem = () => state.items.push(state.items.length + 1)\n    const deleteItem = () => state.items.pop()\n    const updateNested = () => (state.nested.value += '!')\n\n    // Check 'in' operator\n    const hasCount = ref('count' in state)\n\n    // Check keys iteration\n    const keys = ref(Object.keys(state))\n\n    return {\n      state,\n      incrementCount,\n      pushItem,\n      deleteItem,\n      updateNested,\n      hasCount,\n      keys,\n    }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Proxy Handler Improvement Example</h2>\n\n    <div>\n      <p>Count: {{ state.count }}</p>\n      <button @click=\"incrementCount\">Increment</button>\n    </div>\n\n    <div>\n      <p>Items: {{ state.items }}</p>\n      <button @click=\"pushItem\">Push</button>\n      <button @click=\"deleteItem\">Pop</button>\n    </div>\n\n    <div>\n      <p>Nested: {{ state.nested.value }}</p>\n      <button @click=\"updateNested\">Update nested</button>\n    </div>\n\n    <div>\n      <p>Has 'count' property: {{ hasCount }}</p>\n      <p>Object keys: {{ keys }}</p>\n    </div>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, ref } from \"chibivue\";\n\nconst ReactiveCollection = {\n  setup() {\n    const state = reactive({ map: new Map(), set: new Set() });\n\n    const array = ref<number[]>([]);\n    const mutateArray = () => {\n      array.value.push(Date.now());\n    };\n\n    const record = reactive<Record<string, number>>({});\n    const mutateRecord = () => {\n      record[Date.now().toString()] = Date.now();\n    };\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"h1\", {}, [`ReactiveCollection`]),\n\n        h(\"p\", {}, [`array: ${JSON.stringify(array.value)}`]),\n        h(\"button\", { onClick: mutateArray }, [\"update array\"]),\n\n        h(\"p\", {}, [`record: ${JSON.stringify(record)}`]),\n        h(\"button\", { onClick: mutateRecord }, [\"update record\"]),\n\n        h(\"p\", {}, [`map (${state.map.size}): ${JSON.stringify([...state.map])}`]),\n        h(\"button\", { onClick: () => state.map.set(Date.now(), \"item\") }, [\"update map\"]),\n\n        h(\"p\", {}, [`set (${state.set.size}): ${JSON.stringify([...state.set])}`]),\n        h(\"button\", { onClick: () => state.set.add(Date.now()) }, [\"update set\"]),\n      ]);\n  },\n};\n\nconst app = createApp({\n  setup: () => () => h(\"div\", {}, [h(ReactiveCollection, {}, [])]),\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  if (has.call(rawTarget, key)) {\n    return toReactive(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return toReactive(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach() {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, toReactive(value), toReactive(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return { mutableInstrumentations };\n}\n\nfunction createInstrumentationGetter() {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    const { mutableInstrumentations } = createInstrumentations();\n\n    if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(mutableInstrumentations, key) && key in target ? mutableInstrumentations : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(),\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    let parent: ReactiveEffect | undefined = activeEffect;\n    activeEffect = this;\n    const res = this.fn();\n    activeEffect = parent;\n    return res;\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers } from \"./collectionHandlers\";\n\nexport const enum ReactiveFlags {\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (value: V, oldValue: OV) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        cb(newValue, oldValue);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"30_basic_reactivity_system/120_proxy_handler_improvement\", () => {\n  it(\"should handle array methods correctly\", async () => {\n    const state = reactive({ items: [1, 2, 3] });\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [state.items.join(\",\")]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>1,2,3</div>\");\n\n    state.items.push(4);\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>1,2,3,4</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/120_proxy_handler_improvement/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, watch } from \"chibivue\";\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => {\n      state.count++;\n    };\n\n    const unwatch = watch(\n      () => state.count,\n      (newValue, oldValue, cleanup) => {\n        alert(`New value: ${newValue}, old value: ${oldValue}`);\n        cleanup(() => alert(\"Clean Up!\"));\n      },\n    );\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\"button\", { onClick: increment }, [`increment`]),\n        h(\"button\", { onClick: unwatch }, [`unwatch`]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  if (has.call(rawTarget, key)) {\n    return toReactive(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return toReactive(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach() {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, toReactive(value), toReactive(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return { mutableInstrumentations };\n}\n\nfunction createInstrumentationGetter() {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    const { mutableInstrumentations } = createInstrumentations();\n\n    if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(mutableInstrumentations, key) && key in target ? mutableInstrumentations : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(),\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n  ) {}\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers } from \"./collectionHandlers\";\n\nexport const enum ReactiveFlags {\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/component.ts",
    "content": "import type { ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree } = instance;\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n      queueJob(update),\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/tests/e2e.spec.ts",
    "content": "import { describe, expect, it, vi } from \"vitest\";\n\nimport { ref, watch } from \"../packages\";\n\ndescribe(\"30_basic_reactivity_system/130_cleanup_effects\", () => {\n  it(\"should run cleanup on re-run with watch\", async () => {\n    const count = ref(0);\n    const cleanup = vi.fn();\n\n    watch(count, (_newVal, _oldVal, onCleanup) => {\n      onCleanup(cleanup);\n    });\n\n    count.value = 1;\n    await Promise.resolve();\n    // cleanup is set but not called yet\n\n    count.value = 2;\n    await Promise.resolve();\n    expect(cleanup).toHaveBeenCalledTimes(1);\n\n    count.value = 3;\n    await Promise.resolve();\n    expect(cleanup).toHaveBeenCalledTimes(2);\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/130_cleanup_effects/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/examples/playground/src/main.ts",
    "content": "import { createApp, h, reactive, watch } from \"chibivue\";\n\nconst Component = {\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => {\n      state.count++;\n    };\n\n    const unwatch = watch(\n      () => state.count,\n      (newValue, oldValue, cleanup) => {\n        alert(`New value: ${newValue}, old value: ${oldValue}`);\n        cleanup(() => alert(\"Clean Up!\"));\n      },\n    );\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\"button\", { onClick: increment }, [`increment`]),\n        h(\"button\", { onClick: unwatch }, [`unwatch`]),\n      ]);\n  },\n};\n\nconst app = createApp({\n  setup() {\n    const isMounted = reactive({ value: false });\n    const toggle = () => {\n      isMounted.value = !isMounted.value;\n    };\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`isMounted: ${isMounted.value}`]),\n        h(\"button\", { onClick: toggle }, [`toggle`]),\n        isMounted.value ? h(Component, {}, []) : h(\"div\", {}, []),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { reactive } from \"./reactive\";\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key);\n\n    const res = Reflect.get(target, key, receiver);\n    if (isObject(res)) {\n      return reactive(res);\n    }\n\n    return res;\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key];\n    Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n    return true;\n  },\n\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  if (has.call(rawTarget, key)) {\n    return toReactive(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return toReactive(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach() {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, toReactive(value), toReactive(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return { mutableInstrumentations };\n}\n\nfunction createInstrumentationGetter() {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    const { mutableInstrumentations } = createInstrumentations();\n\n    if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(mutableInstrumentations, key) && key in target ? mutableInstrumentations : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(),\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/index.ts",
    "content": "export { ref, shallowRef, triggerRef, toRef, toRefs, isRef, type Ref } from \"./ref\";\nexport { reactive } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers } from \"./collectionHandlers\";\n\nexport const enum ReactiveFlags {\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    instance.scope.on();\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n    instance.scope.off();\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope } = instance;\n    scope.stop();\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/tests/e2e.spec.ts",
    "content": "import { describe, expect, it, vi } from \"vitest\";\n\nimport { EffectScope, ReactiveEffect, ref } from \"../packages\";\n\ndescribe(\"30_basic_reactivity_system/140_effect_scope\", () => {\n  it(\"should run function in scope\", () => {\n    const scope = new EffectScope();\n    const result = scope.run(() => {\n      return 42;\n    });\n\n    expect(result).toBe(42);\n  });\n\n  it(\"should stop effects in scope\", () => {\n    const count = ref(0);\n    const callback = vi.fn();\n\n    const scope = new EffectScope();\n    scope.run(() => {\n      const effect = new ReactiveEffect(\n        () => {\n          callback(count.value);\n        },\n        null,\n        scope,\n      );\n      effect.run();\n    });\n\n    expect(callback).toHaveBeenCalledWith(0);\n    expect(scope.effects.length).toBe(1);\n\n    scope.stop();\n    expect(scope.active).toBe(false);\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/140_effect_scope/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/examples/playground/src/main.ts",
    "content": "import { createApp, customRef, h } from \"chibivue\";\n\nexport function useDebouncedRef<T>(value: T, delay = 1000) {\n  let timeout: number;\n  return customRef((track, trigger) => {\n    return {\n      get() {\n        track();\n        return value;\n      },\n      set(newValue: T) {\n        window.clearTimeout(timeout);\n        timeout = window.setTimeout(() => {\n          value = newValue;\n          trigger();\n        }, delay);\n      },\n    };\n  });\n}\n\nconst CustomRef = {\n  setup() {\n    const text = useDebouncedRef(\"hello\");\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"h1\", {}, [\"CustomRef\"]),\n        h(\"p\", {}, [`${text.value}`]),\n        h(\n          \"input\",\n          {\n            value: text.value,\n            onInput: (e: any) => (text.value = e.target.value),\n          },\n          [],\n        ),\n      ]);\n  },\n};\n\nconst app = createApp({\n  setup() {\n    return () => h(\"div\", {}, [h(CustomRef, {}, [])]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/examples/playground/vite.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: `${process.cwd()}/../../packages`,\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ..._args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    instance.scope.on();\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n    instance.scope.off();\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, queueJob } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope } = instance;\n    scope.stop();\n    unmount(subTree);\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/scheduler.ts",
    "content": "export interface SchedulerJob extends Function {\n  id?: number;\n}\n\nconst queue: SchedulerJob[] = [];\n\nlet flushIndex = 0;\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/tests/e2e.spec.ts",
    "content": "import { describe, expect, it } from \"vitest\";\n\nimport { customRef, isRef, ref, unref } from \"../packages\";\n\ndescribe(\"30_basic_reactivity_system/150_other_apis\", () => {\n  it(\"isRef should identify refs\", () => {\n    const r = ref(0);\n    expect(isRef(r)).toBe(true);\n    expect(isRef(0)).toBe(false);\n  });\n\n  it(\"unref should unwrap refs\", () => {\n    const r = ref(5);\n    expect(unref(r)).toBe(5);\n    expect(unref(5)).toBe(5);\n  });\n\n  it(\"customRef should work\", () => {\n    const r = customRef((track, trigger) => ({\n      get: () => {\n        track();\n        return 42;\n      },\n      set: () => {\n        trigger();\n      },\n    }));\n    expect(r.value).toBe(42);\n  });\n});\n"
  },
  {
    "path": "book/impls/30_basic_reactivity_system/150_other_apis/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/examples/playground/src/main.ts",
    "content": "import {\n  createApp,\n  h,\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  ref,\n} from \"chibivue\";\n\nconst Child = {\n  setup() {\n    const count = ref(0);\n    onBeforeMount(() => {\n      console.log(\"onBeforeMount\");\n    });\n\n    onUnmounted(() => {\n      console.log(\"onUnmounted\");\n    });\n\n    onBeforeUnmount(() => {\n      console.log(\"onBeforeUnmount\");\n    });\n\n    onBeforeUpdate(() => {\n      console.log(\"onBeforeUpdate\");\n    });\n\n    onUpdated(() => {\n      console.log(\"onUpdated\");\n    });\n\n    onMounted(() => {\n      console.log(\"onMounted\");\n    });\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [`${count.value}`]),\n        h(\"button\", { onClick: () => count.value++ }, [\"increment\"]),\n      ]);\n  },\n};\n\nconst app = createApp({\n  setup() {\n    const mountFlag = ref(true);\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"button\", { onClick: () => (mountFlag.value = !mountFlag.value) }, [\"toggle\"]),\n        mountFlag.value ? h(Child, {}, []) : h(\"p\", {}, [\"unmounted\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        render(rootComponent, rootContainer);\n      },\n    };\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(vnode: VNode): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    setCurrentInstance(instance);\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/componentOptions.ts",
    "content": "export type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: Component,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor);\n    } else {\n      patchElement(n1, n2, anchor);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor);\n    }\n  };\n\n  const patchElement = (n1: VNode, n2: VNode, anchor: RendererElement | null) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState, bm, m, bu, u } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (rootComponent, container) => {\n    const vnode = createVNode(rootComponent, {}, []);\n    patch(null, vnode, container, null);\n    flushPostFlushCbs();\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h, onMounted, onUpdated, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/010_lifecycle_hooks\", () => {\n  it(\"should call onMounted hook\", () => {\n    const mounted = vi.fn();\n\n    const app = createApp({\n      setup() {\n        onMounted(mounted);\n        return () => h(\"div\", {}, [\"test\"]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(mounted).toHaveBeenCalledTimes(1);\n  });\n\n  it(\"should call onUpdated hook\", async () => {\n    const updated = vi.fn();\n    const count = ref(0);\n\n    const app = createApp({\n      setup() {\n        onUpdated(updated);\n        return () => h(\"div\", {}, [`count: ${count.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(updated).not.toHaveBeenCalled();\n\n    count.value = 1;\n    await Promise.resolve();\n    expect(updated).toHaveBeenCalledTimes(1);\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/010_lifecycle_hooks/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/examples/playground/src/main.ts",
    "content": "import { type InjectionKey, createApp, h, inject, provide, reactive } from \"chibivue\";\n\nconst Child = {\n  setup() {\n    const rootState = inject<{ count: number }>(\"RootState\");\n    const logger = inject(LoggerKey);\n\n    const action = () => {\n      rootState && rootState.count++;\n      logger?.(\"Hello from Child.\");\n    };\n\n    return () => h(\"button\", { onClick: action }, [\"action\"]);\n  },\n};\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 1 });\n    provide(\"RootState\", state);\n\n    return () => h(\"div\", {}, [h(\"p\", {}, [`${state.count}`]), h(Child, {}, [])]);\n  },\n});\n\ntype Logger = (...args: any) => void;\nconst LoggerKey = Symbol() as InjectionKey<Logger>;\n\napp.provide(LoggerKey, window.console.log);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n  if (component.setup) {\n    setCurrentInstance(instance);\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/componentOptions.ts",
    "content": "import { Data } from \"./component\";\n\nexport type ComponentOptions = {\n  props?: Record<string, any>;\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void;\n  render?: Function;\n  template?: string;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  Component,\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, createVNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor, parentComponent);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState, bm, m, bu, u } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(setupState)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(setupState));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, inject, provide } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/020_provide_inject\", () => {\n  it(\"should provide and inject values\", () => {\n    const Child = {\n      setup() {\n        const message = inject<string>(\"message\");\n        return () => h(\"span\", {}, [message || \"no message\"]);\n      },\n    };\n\n    const app = createApp({\n      setup() {\n        provide(\"message\", \"Hello from parent\");\n        return () => h(\"div\", {}, [h(Child, {}, [])]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div><span>Hello from parent</span></div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/020_provide_inject/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/examples/playground/src/main.ts",
    "content": "import { type PropType, createApp, defineComponent, h, reactive, ref } from \"chibivue\";\n\nconst Child = defineComponent({\n  props: {\n    parentCount: {\n      type: Number as PropType<number>,\n    },\n  },\n  setup() {\n    const count = ref(0);\n    return { count };\n  },\n  render(ctx) {\n    return h(\"div\", {}, [\n      h(\"p\", {}, [`child count: ${ctx.count.value}`]),\n      h(\n        \"button\",\n        {\n          onClick: () => ctx.count.value++,\n          // ^?\n        },\n        [`increment(child)`],\n      ),\n\n      h(\"p\", {}, [\n        `parent count: ${\n          ctx.parentCount\n          // ^?\n        }`,\n      ]),\n    ]);\n  },\n});\n\nconst Child2 = {\n  setup() {\n    const state = reactive({ count: 0 });\n    return { state };\n  },\n  template: `\n    <div>\n      <p>child2 count: {{ state.count }}</p>\n    </div>\n    `,\n};\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 });\n\n    return () =>\n      h(\"div\", {}, [\n        h(\"p\", {}, [\n          h(Child, { parentCount: state.count }, []),\n          h(\"button\", { onClick: () => state.count++ }, [`increment (parent)`]),\n          h(Child2, {}, []),\n        ]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\nimport type { ComponentPublicInstanceConstructor } from \"./componentPublicInstance\";\n\ntype DefineComponent<\n  PropsOrPropOptions = {},\n  RawBindings = {},\n> = ComponentPublicInstanceConstructor<PropsOrPropOptions, RawBindings>;\n\nexport function defineComponent<PropsOptions, RawBindings>(\n  options: ComponentOptions<PropsOptions, RawBindings>,\n): DefineComponent<PropsOptions, RawBindings> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n\n  proxy: ComponentPublicInstance | null;\n  ctx: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n\n    proxy: null,\n    ctx: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const component = instance.type as Component;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (component.setup) {\n    setCurrentInstance(instance);\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = setupResult;\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !component.render) {\n    const template = component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  const { render } = component;\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/componentOptions.ts",
    "content": "import type { PropType } from \"./componentProps\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<P = {}, B = {}> = {\n  props?: P;\n  setup?: (\n    props: InferPropTypes<P>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => (() => VNode) | B;\n  render?: (ctx: ComponentPublicInstance<InferPropTypes<P>, B>) => VNode;\n  template?: string;\n};\n\ntype InferPropTypes<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings> = ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<P = {}, B = {}> = {\n  $: ComponentInternalInstance;\n} & P &\n  B;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, setupState, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/030_component_proxy\", () => {\n  it(\"should access setup return values through component proxy\", async () => {\n    const count = ref(0);\n    const app = createApp({\n      setup() {\n        return () => h(\"div\", {}, [`count: ${count.value}`]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    count.value++;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/030_component_proxy/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, h, ref } from \"chibivue\";\n\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0);\n    const count2 = ref(0);\n    expose({ count });\n    return { count, count2 };\n  },\n  template: `<p>child {{ count }} {{ count2 }}</p>`,\n});\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0);\n    const count2 = ref(0);\n    return { count, count2 };\n  },\n  template: `<p>child2 {{ count }} {{ count2 }}</p>`,\n});\n\nconst app = createApp({\n  setup() {\n    const child = ref();\n    const child2 = ref();\n\n    const increment = () => {\n      child.value.count++;\n      child.value.count2++; // cannot access\n      child2.value.count++;\n      child2.value.count2++;\n    };\n\n    return () =>\n      h(\"div\", {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h(\"button\", { onClick: increment }, [\"increment\"]),\n      ]);\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\nimport type { ComponentPublicInstanceConstructor } from \"./componentPublicInstance\";\n\ntype DefineComponent<\n  PropsOrPropOptions = {},\n  RawBindings = {},\n> = ComponentPublicInstanceConstructor<PropsOrPropOptions, RawBindings>;\n\nexport function defineComponent<PropsOptions, RawBindings>(\n  options: ComponentOptions<PropsOptions, RawBindings>,\n): DefineComponent<PropsOptions, RawBindings> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext = {\n  emit: (e: string, ...args: any[]) => void;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  emit: (event: string, ...args: any[]) => void;\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    emit: null!, // to be set immediately\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode;\n  initProps(instance, props);\n\n  const { setup, render, template } = instance.type as Component;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/componentOptions.ts",
    "content": "import type { SetupContext } from \"./component\";\nimport type { PropType } from \"./componentProps\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<P = {}, B = {}> = {\n  props?: P;\n  setup?: (props: InferPropTypes<P>, ctx: SetupContext) => (() => VNode) | B;\n  render?: (ctx: ComponentPublicInstance<InferPropTypes<P>, B>) => VNode;\n  template?: string;\n};\n\ntype InferPropTypes<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings> = ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<P = {}, B = {}> = {\n  $: ComponentInternalInstance;\n} & P &\n  B;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/h.ts",
    "content": "import { type VNode, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: (VNode | string)[]) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: VNodeNormalizedChildren,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n  normalizeChildren(vnode, children);\n  return vnode;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/040_setup_context\", () => {\n  it(\"should emit events through setup context\", async () => {\n    const handler = vi.fn();\n\n    const Child = {\n      emits: [\"update\"],\n      setup(_: any, { emit }: { emit: (event: string, ...args: any[]) => void }) {\n        return () =>\n          h(\n            \"button\",\n            {\n              onClick: () => emit(\"update\", \"new value\"),\n            },\n            [\"click\"],\n          );\n      },\n    };\n\n    const app = createApp({\n      setup() {\n        return () => h(Child, { onUpdate: handler }, []);\n      },\n    });\n    app.mount(\"#host\");\n\n    const btn = host.querySelector(\"button\") as HTMLButtonElement;\n    btn.click();\n    expect(handler).toHaveBeenCalledWith(\"new value\");\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/040_setup_context/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, h, ref } from \"chibivue\";\n\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () =>\n      h(\"div\", {}, [\n        h(\"h1\", {}, [\"this is my component\"]),\n        h(\"div\", { style: \"border: 1px solid black;\" }, [\n          h(\"h2\", { style: \"border-bottom: 1px solid black;\" }, [\"slotted\"]),\n          slots.default?.(),\n        ]),\n      ]);\n  },\n});\n\nconst app = createApp({\n  setup() {\n    const count = ref(0);\n    return () =>\n      h(MyComponent, {}, () =>\n        h(\"div\", {}, [\n          h(\"button\", { onClick: () => count.value++ }, [\"increment\"]),\n          h(\"p\", {}, [`count is ${count.value}`]),\n        ]),\n      );\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\nimport type { ComponentPublicInstanceConstructor } from \"./componentPublicInstance\";\n\ntype DefineComponent<\n  PropsOrPropOptions = {},\n  RawBindings = {},\n> = ComponentPublicInstanceConstructor<PropsOrPropOptions, RawBindings>;\n\nexport function defineComponent<PropsOptions, RawBindings>(\n  options: ComponentOptions<PropsOptions, RawBindings>,\n): DefineComponent<PropsOptions, RawBindings> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: (e: string, ...args: any[]) => void;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as Component;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/componentOptions.ts",
    "content": "import type { SetupContext } from \"./component\";\nimport type { PropType } from \"./componentProps\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<P = {}, B = {}> = {\n  props?: P;\n  setup?: (props: InferPropTypes<P>, ctx: SetupContext) => (() => VNode) | B;\n  render?: (ctx: ComponentPublicInstance<InferPropTypes<P>, B>) => VNode;\n  template?: string;\n};\n\ntype InferPropTypes<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings> = ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<P = {}, B = {}> = {\n  $: ComponentInternalInstance;\n} & P &\n  B;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n\nexport function withCtx(\n  fn: Function,\n  ctx: ComponentInternalInstance | null = currentRenderingInstance,\n) {\n  if (!ctx) return fn;\n\n  const renderFnWithContext = (...args: any[]) => {\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    try {\n      return fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n    }\n  };\n\n  return renderFnWithContext;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { withCtx } from \"./componentRenderContext\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const prev = setCurrentRenderingInstance(instance);\n        const nextTree = normalizeVNode(render(proxy!));\n        setCurrentRenderingInstance(prev);\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/050_component_slot\", () => {\n  it(\"should render slot content\", () => {\n    const Child = {\n      setup(_: any, { slots }: { slots: any }) {\n        return () => h(\"div\", {}, [slots.default ? slots.default() : \"no slot\"]);\n      },\n    };\n\n    const app = createApp({\n      setup() {\n        // At this stage, slot is passed as a function (default slot only)\n        return () => h(Child, {}, () => h(\"span\", {}, [\"slot content\"]));\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div><span>slot content</span></div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/050_component_slot/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, h } from \"chibivue\";\n\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () =>\n      h(\"div\", {}, [\n        slots.default?.(),\n        h(\"br\", {}, []),\n        slots.myNamedSlot?.(),\n        h(\"br\", {}, []),\n        slots.myScopedSlot2?.({ message: \"hello!\" }),\n      ]);\n  },\n});\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h(\n        MyComponent,\n        {},\n        {\n          default: () => \"hello\",\n          myNamedSlot: () => \"hello2\",\n          myScopedSlot2: (scope: { message: string }) => `message: ${scope.message}`,\n        },\n      );\n  },\n});\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { ComponentOptions } from \"./componentOptions\";\nimport type { ComponentPublicInstanceConstructor } from \"./componentPublicInstance\";\n\ntype DefineComponent<\n  PropsOrPropOptions = {},\n  RawBindings = {},\n> = ComponentPublicInstanceConstructor<PropsOrPropOptions, RawBindings>;\n\nexport function defineComponent<PropsOptions, RawBindings>(\n  options: ComponentOptions<PropsOptions, RawBindings>,\n): DefineComponent<PropsOptions, RawBindings> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { emit } from \"./componentEmits\";\nimport type { ComponentOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Component = ComponentOptions;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: (e: string, ...args: any[]) => void;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: Component;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as Component;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/componentEmits.ts",
    "content": "import { camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/componentOptions.ts",
    "content": "import type { SetupContext } from \"./component\";\nimport type { PropType } from \"./componentProps\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<P = {}, B = {}> = {\n  props?: P;\n  setup?: (props: InferPropTypes<P>, ctx: SetupContext) => (() => VNode) | B;\n  render?: (ctx: ComponentPublicInstance<InferPropTypes<P>, B>) => VNode;\n  template?: string;\n};\n\ntype InferPropTypes<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings> = ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<P = {}, B = {}> = {\n  $: ComponentInternalInstance;\n} & P &\n  B;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/packages/shared/typeUtils.ts",
    "content": "export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/060_slot_extend\", () => {\n  it(\"should render named slots\", () => {\n    const Child = {\n      setup(_: any, { slots }: { slots: any }) {\n        return () =>\n          h(\"div\", {}, [\n            h(\"header\", {}, [slots.header ? slots.header() : null]),\n            h(\"main\", {}, [slots.default ? slots.default() : null]),\n            h(\"footer\", {}, [slots.footer ? slots.footer() : null]),\n          ]);\n      },\n    };\n\n    const app = createApp({\n      setup() {\n        return () =>\n          h(\n            Child,\n            {},\n            {\n              header: () => h(\"span\", {}, [\"Header\"]),\n              default: () => h(\"span\", {}, [\"Content\"]),\n              footer: () => h(\"span\", {}, [\"Footer\"]),\n            },\n          );\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\n      \"<div><header><span>Header</span></header><main><span>Content</span></main><footer><span>Footer</span></footer></div>\",\n    );\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/060_slot_extend/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/examples/playground/src/main.ts",
    "content": "import { computed, createApp, defineComponent, h, ref } from \"chibivue\";\n\nconst CountInjectionKey = Symbol();\n\nconst Child = defineComponent({\n  inject: {\n    injectedCount: { from: CountInjectionKey },\n  },\n\n  emits: { \"update:count\": null },\n\n  render(ctx) {\n    return h(\"div\", {}, [\n      h(\"button\", { onClick: () => ctx.$emit(\"update:count\") }, [\"update count\"]),\n      `injected: ${ctx.injectedCount}`,\n    ]);\n  },\n});\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0);\n    return { count };\n  },\n\n  data() {\n    return { count2: 0 };\n  },\n\n  computed: {\n    double() {\n      return this.count2 * 2;\n    },\n  },\n\n  methods: {\n    hello() {\n      this.count2++;\n    },\n  },\n\n  watch: {\n    count2() {\n      console.log(`count2 changed: ${this.count2}`);\n    },\n  },\n\n  created() {\n    console.log(\"created\");\n  },\n\n  beforeMount() {\n    console.log(\"beforeMount\");\n  },\n\n  mounted() {\n    console.log(\"mounted\");\n  },\n\n  beforeUpdate() {\n    console.log(\"beforeUpdate\");\n  },\n\n  updated() {\n    console.log(\"updated\");\n  },\n\n  beforeUnmount() {\n    console.log(\"beforeUnmount\");\n  },\n\n  unmounted() {\n    console.log(\"unmounted\");\n  },\n\n  render(ctx) {\n    return h(\"div\", {}, [\n      h(\"button\", { onClick: ctx.hello }, [\"hello\"]),\n      h(\"br\", {}, []),\n      `${ctx.count2}\\n`,\n      h(\"br\", {}, []),\n      `${ctx.count}\\n`,\n      h(\"br\", {}, []),\n      h(Child, { \"onUpdate:count\": () => ctx.count2++ }, []),\n    ]);\n  },\n\n  provide() {\n    return { [CountInjectionKey]: computed(() => this.count2) };\n  },\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-core/ast.ts",
    "content": "export const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-core/codegen.ts",
    "content": "import { toHandlerKey } from \"../shared\";\nimport {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[];\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? \"return \" : \"\"}function render(_ctx) {\n  ${option.isBrowser ? \"with (_ctx) {\" : \"\"}\n    const { h } = ChibiVue;\n    return ${genNode(children[0], option)};\n  ${option.isBrowser ? \"}\" : \"\"}\n}`;\n};\n\nconst genNode = (node: TemplateChildNode, option: Required<CompilerOptions>): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option);\n    case NodeTypes.TEXT:\n      return genText(node);\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option);\n    default:\n      return \"\";\n  }\n};\n\nconst genElement = (el: ElementNode, option: Required<CompilerOptions>): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map((prop) => genProp(prop, option))\n    .join(\", \")}}, [${el.children.map((it) => genNode(it, option)).join(\", \")}])`;\n};\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`;\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case \"on\":\n          return `${toHandlerKey(prop.arg)}: ${option.isBrowser ? \"\" : \"_ctx.\"}${prop.exp}`;\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`);\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`);\n  }\n};\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``;\n};\n\nconst genInterpolation = (node: InterpolationNode, option: Required<CompilerOptions>): string => {\n  return `${option.isBrowser ? \"\" : \"_ctx.\"}${node.content}`;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const parseResult = baseParse(template.trim());\n  const code = generate(parseResult, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-core/options.ts",
    "content": "export type CompilerOptions = {\n  isBrowser?: boolean;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return { children: children };\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"40_basic_component_system/070_options_api\", () => {\n  it(\"should support options API with methods\", () => {\n    const app = createApp({\n      data() {\n        return { count: 0 };\n      },\n      methods: {\n        increment() {\n          this.count++;\n        },\n      },\n      template: `<div>count: {{ count }}</div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/40_basic_component_system/070_options_api/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, ref } from \"chibivue\";\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0);\n    return { count };\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> Hello World! </p>\n      <p> Count: {{ count }} </p>\n    </div>\n  `,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode = VNodeCall | ObjectExpression | ArrayExpression | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode: (TemplateChildNode | VNodeCall)[] | undefined;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: string;\n  arg: string;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  ctxIdent: \"_ctx\",\n};\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: Required<CompilerOptions>): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.children) {\n    ast.children.forEach((codegenNode) => {\n      genNode(codegenNode, context, option);\n    });\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(_ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = [CONSTANT.vNodeFuncName].join(\", \");\n  push(`const { ${helpers} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: Required<CompilerOptions>) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  if (!option.isBrowser) {\n    push(`${CONSTANT.ctxIdent}.`);\n  }\n  push(node.content);\n}\n\nfunction genExpressionAsPropertyKey(node: ExpressionNode, context: CodegenContext) {\n  const { push } = context;\n  if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: Required<CompilerOptions>) {\n  const { push } = context;\n  const { tag, props, children } = node;\n\n  push(CONSTANT.vNodeFuncName + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformElement], {}];\n}\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: { ...directiveTransforms },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = {\n  isBrowser?: boolean;\n};\n\nexport interface TransformOptions {\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport { NodeTypes, type ParentNode, type RootNode, type TemplateChildNode } from \"./ast\";\nimport type { TransformOptions } from \"./options\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\n// TODO:\nexport type DirectiveTransform = Function;\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n  };\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport type { NodeTransform } from \"../transform\";\n\nexport type PropsExpression = ObjectExpression;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(node: ElementNode): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      // TODO:\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n  if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/index.ts",
    "content": "export type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/010_transformer\", () => {\n  it(\"should compile and render template\", () => {\n    const app = createApp({\n      template: `<div>Hello World</div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>Hello World</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/010_transformer/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent } from \"chibivue\";\n\nconst App = defineComponent({\n  setup() {\n    const bind = { id: \"some-id\", class: \"some-class\", style: \"color: red\" };\n    return { count: 1, bind };\n  },\n\n  template: `<div>\n  <p v-bind:id=\"count\"> v-bind:id=\"count\" </p>\n  <p :id=\"count * 2\"> :id=\"count * 2\" </p>\n\n  <p v-bind:[\"style\"]=\"bind.style\"> v-bind:[\"style\"]=\"bind.style\" </p>\n  <p :[\"style\"]=\"bind.style\"> :[\"style\"]=\"bind.style\" </p>\n\n  <p v-bind=\"bind\"> v-bind=\"bind\" </p>\n\n  <p :style=\"{ 'font-weight': 'bold' }\"> :style=\"{ font-weight: 'bold' }\" </p>\n  <p :style=\"'font-weight: bold;'\"> :style=\"'font-weight: bold;'\" </p>\n  <p :class=\"'my-class my-class2'\"> :class=\"'my-class my-class2'\" </p>\n  <p :class=\"['my-class']\"> :class=\"['my-class']\" </p>\n  <p :class=\"{ 'my-class': true }\"> :class=\"{ 'my-class': true }\" </p>\n  <p :class=\"{ 'my-class': false }\"> :class=\"{ 'my-class': false }\" </p>\n</div>`,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode: (TemplateChildNode | VNodeCall)[] | undefined;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: string;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  mergeProps: \"mergeProps\",\n  normalizeClass: \"normalizeClass\",\n  normalizeStyle: \"normalizeStyle\",\n  normalizeProps: \"normalizeProps\",\n  ctxIdent: \"_ctx\",\n};\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: Required<CompilerOptions>): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.children) {\n    ast.children.forEach((codegenNode) => {\n      genNode(codegenNode, context, option);\n    });\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(_ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = [\n    CONSTANT.vNodeFuncName,\n    CONSTANT.mergeProps,\n    CONSTANT.normalizeProps,\n    CONSTANT.normalizeClass,\n    CONSTANT.normalizeStyle,\n  ].join(\", \");\n  push(`const { ${helpers} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: Required<CompilerOptions>) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  if (!option.isBrowser) {\n    push(`${CONSTANT.ctxIdent}.`);\n  }\n  push(node.content);\n}\n\nfunction genExpressionAsPropertyKey(node: ExpressionNode, context: CodegenContext) {\n  const { push } = context;\n  if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: Required<CompilerOptions>) {\n  const { push } = context;\n  const { tag, props, children } = node;\n\n  push(CONSTANT.vNodeFuncName + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  const callee = node.callee;\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformBind } from \"./transforms/vBind\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformElement], { bind: transformBind }];\n}\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: { ...directiveTransforms },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = {\n  isBrowser?: boolean;\n};\n\nexport interface TransformOptions {\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n  };\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      // const isVOn = name === \"on\"; // TODO:\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && /*(*/ isVBind /* || isVOn) */) {\n        if (exp) {\n          if (isVBind /* || isVOn */) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // TODO: v-on\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        // TODO: v-on\n\n        properties.push(...props);\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(\"mergeProps\", mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(\"normalizeClass\", [classProp.value]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(\"normalizeStyle\", [styleProp.value]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(\"normalizeProps\", [propsExpression]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(\"normalizeProps\", [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/transforms/vBind.ts",
    "content": "import { createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, type Data, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/020_v_bind\", () => {\n  it(\"should bind attribute with v-bind\", () => {\n    const app = createApp({\n      setup() {\n        const state = reactive({ id: \"my-id\" });\n        return { state };\n      },\n      template: `<div v-bind:id=\"state.id\">content</div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe('<div id=\"my-id\">content</div>');\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/020_v_bind/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button :onClick=\"increment\">count + count is: {{ count + count }}</button>\n  </div>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode: (TemplateChildNode | VNodeCall)[] | undefined;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(root: Node, onIdentifier: (node: Identifier) => void) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        onIdentifier(node);\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  mergeProps: \"mergeProps\",\n  normalizeClass: \"normalizeClass\",\n  normalizeStyle: \"normalizeStyle\",\n  normalizeProps: \"normalizeProps\",\n  ctxIdent: \"_ctx\",\n};\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: Required<CompilerOptions>): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.children) {\n    ast.children.forEach((codegenNode) => {\n      genNode(codegenNode, context, option);\n    });\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(_ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = [\n    CONSTANT.vNodeFuncName,\n    CONSTANT.mergeProps,\n    CONSTANT.normalizeProps,\n    CONSTANT.normalizeClass,\n    CONSTANT.normalizeStyle,\n  ].join(\", \");\n  push(`const { ${helpers} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: Required<CompilerOptions>) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: Required<CompilerOptions>) {\n  const { push } = context;\n  const { tag, props, children } = node;\n\n  push(CONSTANT.vNodeFuncName + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  const callee = node.callee;\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformExpression, transformElement], { bind: transformBind }];\n}\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: { ...directiveTransforms },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n  };\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      // const isVOn = name === \"on\"; // TODO:\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && /*(*/ isVBind /* || isVOn) */) {\n        if (exp) {\n          if (isVBind /* || isVOn */) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // TODO: v-on\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        // TODO: v-on\n\n        properties.push(...props);\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(\"mergeProps\", mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(\"normalizeClass\", [classProp.value]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(\"normalizeStyle\", [styleProp.value]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(\"normalizeProps\", [propsExpression]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(\"normalizeProps\", [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n\n  walkIdentifiers(ast, (node) => {\n    node.name = rewriteIdentifier(node.name);\n    ids.push(node as QualifiedId);\n  });\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, type Data, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/022_transform_expression\", () => {\n  it(\"should transform expressions in template\", () => {\n    const app = createApp({\n      setup() {\n        const state = reactive({ count: 5 });\n        return { state };\n      },\n      template: `<div>count: {{ state.count }}</div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 5</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/022_transform_expression/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, ref } from \"chibivue\";\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0);\n    const increment = (e: Event) => {\n      console.log(e);\n      count.value++;\n    };\n    return { count, increment };\n  },\n\n  template: `<div>\n    <p>count: {{ count }}</p>\n\n    <button v-on:click=\"increment\">v-on:click=\"increment\"</button>\n    <button @click=\"increment\">@click=\"increment\"</button>\n    <button v-on=\"{ click: increment }\">v-on=\"{ click: increment }\"</button>\n    <button v-on:['click']=\"increment\">v-on:['click']=\"increment\"</button>\n    \n    <button @click=\"count++\">@click=\"count++\"</button>\n    <button @click=\"() => count++\">@click=\"() => count++\"</button>\n    <button @click=\"increment($event)\">@click=\"increment($event)\"</button>\n    <button @click=\"e => increment(e)\">@click=\"e => increment(e)\"</button>\n</div>`,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode: (TemplateChildNode | VNodeCall)[] | undefined;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        if (!isLocal) {\n          onIdentifier(node);\n        }\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  mergeProps: \"mergeProps\",\n  normalizeClass: \"normalizeClass\",\n  normalizeStyle: \"normalizeStyle\",\n  normalizeProps: \"normalizeProps\",\n  ctxIdent: \"_ctx\",\n};\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: Required<CompilerOptions>): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.children) {\n    ast.children.forEach((codegenNode) => {\n      genNode(codegenNode, context, option);\n    });\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: Required<CompilerOptions>) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: Required<CompilerOptions>) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformExpression, transformElement], { bind: transformBind, on: transformOn }];\n}\n\nexport function baseCompile(template: string, option: Required<CompilerOptions>) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: { ...directiveTransforms },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const CREATE_VNODE = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: \"createVNode\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: string): void;\n  removeIdentifiers(exp: string): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        addId(exp);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        removeId(exp);\n      }\n    },\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(template, defaultOption);\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, type Data, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/025_v_on\", () => {\n  it(\"should handle v-on click events\", () => {\n    const handler = vi.fn();\n    const app = createApp({\n      setup() {\n        return { handler };\n      },\n      template: `<button v-on:click=\"handler\">click</button>`,\n    });\n    app.mount(\"#host\");\n\n    const btn = host.querySelector(\"button\") as HTMLButtonElement;\n    btn.click();\n    expect(handler).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/025_v_on/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, ref } from \"chibivue\";\n\nconst App = defineComponent({\n  setup() {\n    const inputText = ref(\"\");\n\n    const buffer = ref(\"\");\n    const handleInput = (e: Event) => {\n      const target = e.target as HTMLInputElement;\n      buffer.value = target.value;\n    };\n    const submit = () => {\n      inputText.value = buffer.value;\n      buffer.value = \"\";\n    };\n\n    return { inputText, buffer, handleInput, submit };\n  },\n\n  template: `<div>\n    <form @submit.prevent=\"submit\">\n      <input :value=\"buffer\" @input=\"handleInput\" />\n      <button>submit</button>\n    </form>\n    <p>inputText: {{ inputText }}</p>\n</div>`,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode: (TemplateChildNode | VNodeCall)[] | undefined;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        if (!isLocal) {\n          onIdentifier(node);\n        }\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  mergeProps: \"mergeProps\",\n  normalizeClass: \"normalizeClass\",\n  normalizeStyle: \"normalizeStyle\",\n  normalizeProps: \"normalizeProps\",\n  ctxIdent: \"_ctx\",\n};\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.children) {\n    ast.children.forEach((codegenNode) => {\n      genNode(codegenNode, context, option);\n    });\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformExpression, transformElement], { bind: transformBind, on: transformOn }];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const CREATE_VNODE = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: \"createVNode\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: string): void;\n  removeIdentifiers(exp: string): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        addId(exp);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        removeId(exp);\n      }\n    },\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport { createCallExpression, createObjectProperty } from \"../../compiler-core\";\nimport { V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\n\nconst isEventModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self`,\n);\n\nconst resolveModifiers = (modifiers: string[]) => {\n  const eventModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n    if (isEventModifier(modifier)) {\n      eventModifiers.push(modifier);\n    }\n  }\n\n  return { eventModifiers };\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { eventModifiers } = resolveModifiers(modifiers);\n\n    if (eventModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(eventModifiers),\n      ]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, type Data, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/directives/vOn.ts",
    "content": "const modifierGuards: Record<string, (e: Event) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/027_event_modifier\", () => {\n  it(\"should handle event modifiers\", () => {\n    const handler = vi.fn();\n    const app = createApp({\n      setup() {\n        return { handler };\n      },\n      template: `<button @click=\"handler\">click</button>`,\n    });\n    app.mount(\"#host\");\n\n    const btn = host.querySelector(\"button\") as HTMLButtonElement;\n    btn.click();\n    expect(handler).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, ref } from \"chibivue\";\n\nconst App = defineComponent({\n  setup() {\n    const inputText = ref(\"\");\n\n    const buffer = ref(\"\");\n    const handleInput = (e: Event) => {\n      const target = e.target as HTMLInputElement;\n      buffer.value = target.value;\n    };\n    const submit = () => {\n      inputText.value = buffer.value;\n      buffer.value = \"\";\n    };\n\n    return { inputText, buffer, handleInput, submit };\n  },\n\n  template: `<div>\n    <form>\n      <label>\n        Input Data\n        <input \n          :value=\"buffer\" \n          @input=\"handleInput\" \n          @keydown.prevent.meta.enter=\"submit\" \n        />\n      </label>\n    </form>\n    <p>inputText: {{ inputText }}</p>\n</div>`,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode: (TemplateChildNode | VNodeCall)[] | undefined;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        if (!isLocal) {\n          onIdentifier(node);\n        }\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  mergeProps: \"mergeProps\",\n  normalizeClass: \"normalizeClass\",\n  normalizeStyle: \"normalizeStyle\",\n  normalizeProps: \"normalizeProps\",\n  ctxIdent: \"_ctx\",\n};\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.children) {\n    ast.children.forEach((codegenNode) => {\n      genNode(codegenNode, context, option);\n    });\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformExpression, transformElement], { bind: transformBind, on: transformOn }];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const CREATE_VNODE = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: \"createVNode\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: string): void;\n  removeIdentifiers(exp: string): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        addId(exp);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        removeId(exp);\n      }\n    },\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/h.ts",
    "content": "import { type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(type: string | object, props: VNodeProps, children: any) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el } = vnode;\n    hostRemove(el!);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, type Data, currentInstance } from \"./component\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes = string | typeof Text | object;\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/027_event_modifier2\", () => {\n  it(\"should handle event modifiers with shorthand\", () => {\n    const handler = vi.fn();\n    const app = createApp({\n      setup() {\n        return { handler };\n      },\n      template: `<button @click.prevent=\"handler\">click</button>`,\n    });\n    app.mount(\"#host\");\n\n    const btn = host.querySelector(\"button\") as HTMLButtonElement;\n    const event = new MouseEvent(\"click\", { bubbles: true, cancelable: true });\n    btn.dispatchEvent(event);\n    expect(handler).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/027_event_modifier2/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/examples/playground/src/main.ts",
    "content": "import { Fragment, createApp, defineComponent, h, ref } from \"chibivue\";\n\n// const App = defineComponent({\n//   template: `<header>header</header>\n//   <main>main</main>\n//   <footer>footer</footer>`,\n// });\n\nconst App = defineComponent({\n  setup() {\n    const list = ref([0]);\n    const update = () => {\n      list.value = [...list.value, list.value.length];\n    };\n    return () =>\n      h(Fragment, {}, [\n        h(\"button\", { onClick: update }, \"update\"),\n        ...list.value.map((i) => h(\"div\", {}, i)),\n      ]);\n  },\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        if (!isLocal) {\n          onIdentifier(node);\n        }\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = {\n  vNodeFuncName: \"h\",\n  mergeProps: \"mergeProps\",\n  normalizeClass: \"normalizeClass\",\n  normalizeStyle: \"normalizeStyle\",\n  normalizeProps: \"normalizeProps\",\n  ctxIdent: \"_ctx\",\n};\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformExpression, transformElement], { bind: transformBind, on: transformOn }];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: string): void;\n  removeIdentifiers(exp: string): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        addId(exp);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        removeId(exp);\n      }\n    },\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport { type ComponentInternalInstance, type Data, currentInstance } from \"./component\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { RawSlots } from \"./componentSlots\";\n\nexport type VNodeTypes =\n  | string // html element name\n  | typeof Text // html text node\n  | typeof Fragment // fragment\n  | ComponentPublicInstance; // Vue Component\n\nexport const Fragment = Symbol();\n\nexport const Text = Symbol();\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/030_fragment\", () => {\n  it(\"should render fragment (multiple root elements)\", () => {\n    const app = createApp({\n      template: `<span>first</span><span>second</span>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<span>first</span><span>second</span>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/030_fragment/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent } from \"chibivue\";\n\nconst App = defineComponent({\n  template: `\n  <!-- this is heder. -->\n  <header>header</header>\n\n  <!-- \n    this is main.\n    main content is here!\n  -->\n  <main>main</main>\n\n  <!-- this is footer -->\n  <footer>footer</footer>`,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode | CommentNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        if (!isLocal) {\n          onIdentifier(node);\n        }\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformExpression, transformElement], { bind: transformBind, on: transformOn }];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: string): void;\n  removeIdentifiers(exp: string): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        addId(exp);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        removeId(exp);\n      }\n    },\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    traverseNode(child, context);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, createCommentVNode, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/035_comment\", () => {\n  it(\"should render comments using createCommentVNode\", () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", {}, [createCommentVNode(\" comment \")]);\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div><!-- comment --></div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/035_comment/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/examples/playground/src/main.ts",
    "content": "import { createApp, defineComponent, ref } from \"chibivue\";\n\nconst App = defineComponent({\n  setup() {\n    const n = ref(1);\n    const inc = () => {\n      n.value++;\n    };\n\n    return { n, inc };\n  },\n\n  template: `\n    <button @click=\"inc\">inc</button>\n    <p v-if=\"n % 5 === 0 && n % 3 === 0\">FizzBuzz</p>\n    <p v-else-if=\"n % 5 === 0\">Buzz</p>\n    <p v-else-if=\"n % 3 === 0\">Fizz</p>\n    <p v-else>{{ n }}</p>\n  `,\n});\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/examples/playground/vite.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: `${process.cwd()}/../../packages`,\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/babelUtils.ts",
    "content": "import type { Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n) {\n  (walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        if (!isLocal) {\n          onIdentifier(node);\n        }\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformExpression, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: string): void;\n  removeIdentifiers(exp: string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        addId(exp);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        removeId(exp);\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const ast = parse(`(${rawExp})`).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-core/utils.ts",
    "content": "import { type JSChildNode, NodeTypes, type Position, type SimpleExpressionNode } from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, anchor, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/040_v_if_and_structural_directive\", () => {\n  it(\"should render conditionally with v-if\", async () => {\n    const show = ref(true);\n    const app = createApp({\n      setup() {\n        return { show };\n      },\n      template: `<div v-if=\"show\">visible</div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>visible</div>\");\n\n    show.value = false;\n    await Promise.resolve();\n    expect(host.innerHTML).toBe(\"<!---->\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/040_v_if_and_structural_directive/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/src/App.vue",
    "content": "<script>\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst genId = () => Math.random().toString(36).slice(2)\n\nconst FRUITS_FACTORIES = [\n  () => ({ id: genId(), name: 'apple', color: 'red' }),\n  () => ({ id: genId(), name: 'banana', color: 'yellow' }),\n  () => ({ id: genId(), name: 'grape', color: 'purple' }),\n]\n\nexport default {\n  setup() {\n    const fruits = ref([...FRUITS_FACTORIES].map(f => f()))\n    const addFruit = () => {\n      fruits.value.push(\n        FRUITS_FACTORIES[Math.floor(Math.random() * FRUITS_FACTORIES.length)](),\n      )\n    }\n    return { fruits, addFruit }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"addFruit\">add fruits!</button>\n\n  <!-- basic -->\n  <ul>\n    <li v-for=\"fruit in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- indexed -->\n  <ul>\n    <li v-for=\"(fruit, i) in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- destructuring -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">{{ name }}</span>\n    </li>\n  </ul>\n\n  <!-- object -->\n  <ul>\n    <li v-for=\"(value, key, idx) in fruits[0]\" :key=\"key\">\n      [{{ idx }}] {{ key }}: {{ value }}\n    </li>\n  </ul>\n\n  <!-- range -->\n  <ul>\n    <li v-for=\"n in 10\">{{ n }}</li>\n  </ul>\n\n  <!-- string -->\n  <ul>\n    <li v-for=\"c in 'hello'\">{{ c }}</li>\n  </ul>\n\n  <!-- nested -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">\n        <span v-for=\"n in 3\">{{ n }}</span>\n        {{ name }}</span\n      >\n    </li>\n  </ul>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/examples/playground/vite.config.js",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, helperNameMap } from \"./runtimeHelpers\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport { advancePositionWithClone } from \"./utils\";\n\nexport interface ParserContext {\n  readonly originalSource: string;\n\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string): RootNode => {\n  const context = createParserContext(content);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RENDER_LIST]: `renderList`,\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n\n    const vnodeTag = `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-core/utils.ts",
    "content": "import {\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type SimpleExpressionNode,\n  type SourceLocation,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n\n  template?: string;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, reactive } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/050_v_for\", () => {\n  it(\"should render list with v-for\", () => {\n    const app = createApp({\n      setup() {\n        const state = reactive({ items: [1, 2, 3] });\n        return { state };\n      },\n      template: `<ul><li v-for=\"item in state.items\" :key=\"item\">{{ item }}</li></ul>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe('<ul><li key=\"1\">1</li><li key=\"2\">2</li><li key=\"3\">3</li></ul>');\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/050_v_for/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/src/App.vue",
    "content": "<script>\nimport { defineComponent } from 'chibivue'\n\nimport Counter from './components/Counter.vue'\n\nexport default defineComponent({\n  components: { Counter },\n})\n</script>\n\n<template>\n  <Counter />\n  <Counter />\n  <GlobalCounter />\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/src/components/Counter.vue",
    "content": "<script>\nimport { ref, defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n})\n</script>\n\n<template>\n  <button @click=\"count++\">count: {{ count }}</button>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\n// @ts-expect-error\nimport App from \"./App.vue\";\n// @ts-expect-error\nimport Counter from \"./components/Counter.vue\";\n\nconst app = createApp(App);\n\napp.component(\"GlobalCounter\", Counter);\n\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-core/utils.ts",
    "content": "import {\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type SimpleExpressionNode,\n  type SourceLocation,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/060_resolve_components\", () => {\n  it(\"should resolve and render components\", () => {\n    const Child = {\n      template: `<span>child</span>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<div><Child /></div>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div><span>child</span></div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/060_resolve_components/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\nimport Comp from './Comp.vue'\n\nexport default {\n  components: {\n    Comp,\n  },\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n}\n</script>\n\n<template>\n  <Comp>\n    <template #default>\n      <button @click=\"count++\">count is: {{ count }}</button>\n    </template>\n  </Comp>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/src/Comp.vue",
    "content": "<template>\n  <p><slot name=\"default\" /></p>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/080_component_slot_outlet\", () => {\n  it(\"should render slot outlet\", () => {\n    const Child = {\n      template: `<div><slot></slot></div>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      render() {\n        return h(Child, null, {\n          default: () => [\"slot content\"],\n        });\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>slot content</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/080_component_slot_outlet/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\nimport Comp from './Comp.vue'\n\nexport default {\n  components: {\n    Comp,\n  },\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n}\n</script>\n\n<template>\n  <Comp>\n    <template #default>\n      <button @click=\"count++\">count is: {{ count }}</button>\n    </template>\n  </Comp>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/src/Comp.vue",
    "content": "<template>\n  <p><slot name=\"default\" /></p>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | SlotsExpression // slots object for components\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n  isSlot?: boolean;\n}\n\n// SlotsExpression is an ObjectExpression that represents the slots object\n// passed to a component. e.g., { default: () => [...], header: () => [...] }\nexport interface SlotsExpression extends ObjectExpression {}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  if (element.isSelfClosing) {\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName =\n      match[1] ||\n      (startsWith(name, \":\")\n        ? \"bind\"\n        : startsWith(name, \"@\")\n          ? \"on\"\n          : startsWith(name, \"#\")\n            ? \"slot\"\n            : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\nexport const WITH_CTX = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n  [WITH_CTX]: \"withCtx\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type SlotsExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\nimport { buildSlots } from \"./vSlot\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (isComponent) {\n        // For components, build slots object\n        const { slots } = buildSlots(node, context);\n        vnodeChildren = slots as SlotsExpression;\n      } else if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/transforms/vSlot.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type Property,\n  type SlotsExpression,\n  type TemplateChildNode,\n  createCallExpression,\n  createFunctionExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { WITH_CTX } from \"../runtimeHelpers\";\nimport type { TransformContext } from \"../transform\";\nimport { findDir, isStaticExp, isTemplateNode } from \"../utils\";\n\n// Build slots object for a component\nexport function buildSlots(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  slots: SlotsExpression;\n} {\n  const { children } = node;\n  const slotsProperties: Property[] = [];\n\n  // 1. Check for slot with slotProps on component itself.\n  //    <Comp v-slot=\"{ prop }\"/>\n  const onComponentSlot = findDir(node, \"slot\", true);\n  if (onComponentSlot) {\n    const { arg, exp } = onComponentSlot;\n    slotsProperties.push(\n      createObjectProperty(\n        arg || createSimpleExpression(\"default\", true),\n        buildSlotFn(exp, children, node.loc, context),\n      ),\n    );\n  }\n\n  // 2. Iterate through children and check for template slots\n  //    <template v-slot:foo=\"{ prop }\">\n  let hasTemplateSlots = false;\n  const implicitDefaultChildren: TemplateChildNode[] = [];\n\n  for (let i = 0; i < children.length; i++) {\n    const slotElement = children[i];\n    let slotDir: DirectiveNode | undefined;\n\n    if (!isTemplateNode(slotElement) || !(slotDir = findDir(slotElement, \"slot\", true))) {\n      // not a <template v-slot>, skip.\n      if (slotElement.type !== NodeTypes.COMMENT) {\n        implicitDefaultChildren.push(slotElement);\n      }\n      continue;\n    }\n\n    hasTemplateSlots = true;\n    const { children: slotChildren, loc: slotLoc } = slotElement;\n    const { arg: slotName = createSimpleExpression(`default`, true), exp: slotProps } = slotDir;\n\n    const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc, context);\n    slotsProperties.push(createObjectProperty(slotName, slotFunction));\n  }\n\n  if (!onComponentSlot) {\n    if (!hasTemplateSlots) {\n      // implicit default slot (on component)\n      slotsProperties.push(\n        createObjectProperty(`default`, buildSlotFn(undefined, children, node.loc, context)),\n      );\n    } else if (implicitDefaultChildren.length) {\n      // implicit default slot (mixed with named slots)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, implicitDefaultChildren, node.loc, context),\n        ),\n      );\n    }\n  }\n\n  const slots = createObjectExpression(slotsProperties, node.loc) as SlotsExpression;\n\n  return {\n    slots,\n  };\n}\n\nfunction buildSlotFn(\n  props: ExpressionNode | undefined,\n  children: TemplateChildNode[],\n  loc: any,\n  context: TransformContext,\n) {\n  const fn = createFunctionExpression(\n    props,\n    children,\n    false /* newline */,\n    children.length ? children[0].loc : loc,\n  );\n  fn.isSlot = true;\n  return createCallExpression(context.helper(WITH_CTX), [fn], loc);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type PlainElementNode,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n\nexport function isTemplateNode(\n  node: RootNode | TemplateChildNode,\n): node is PlainElementNode & { tag: \"template\" } {\n  return (\n    node.type === NodeTypes.ELEMENT &&\n    node.tagType === ElementTypes.ELEMENT &&\n    node.tag === \"template\"\n  );\n}\n\nexport function findDir(\n  node: ElementNode,\n  name: string | RegExp,\n  allowEmpty: boolean = false,\n): DirectiveNode | undefined {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (\n      p.type === NodeTypes.DIRECTIVE &&\n      (allowEmpty || p.exp) &&\n      (typeof name === \"string\" ? p.name === name : name.test(p.name))\n    ) {\n      return p;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n\nexport function withCtx(\n  fn: Function,\n  ctx: ComponentInternalInstance | null = currentRenderingInstance,\n) {\n  if (!ctx) return fn;\n\n  const renderFnWithContext = (...args: any[]) => {\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    try {\n      return fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n    }\n  };\n\n  return renderFnWithContext;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n\nexport { withCtx } from \"./componentRenderContext\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/085_component_slot_insert\", () => {\n  it(\"should compile slot insertion with template syntax\", () => {\n    const Child = {\n      template: `<div><slot></slot></div>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<Child><template #default>slot content</template></Child>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>slot content</div>\");\n  });\n\n  it(\"should compile named slots with template syntax\", () => {\n    const Child = {\n      template: `<header><slot name=\"header\"></slot></header><main><slot></slot></main>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<Child><template #header>Header Content</template><template #default>Main Content</template></Child>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<header>Header Content</header><main>Main Content</main>\");\n  });\n\n  it(\"should compile implicit default slot\", () => {\n    const Child = {\n      template: `<div><slot></slot></div>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<Child>implicit default content</Child>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>implicit default content</div>\");\n  });\n\n  it(\"should maintain reactivity in slot content\", () => {\n    const Child = {\n      template: `<div><slot></slot></div>`,\n    };\n\n    const count = ref(0);\n    const app = createApp({\n      components: { Child },\n      setup() {\n        return { count };\n      },\n      template: `<Child><template #default>count: {{ count }}</template></Child>`,\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>count: 0</div>\");\n\n    count.value++;\n    return Promise.resolve().then(() => {\n      expect(host.innerHTML).toBe(\"<div>count: 1</div>\");\n    });\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/085_component_slot_insert/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>{{ msg }} will not be compiled</span>\n  </div>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/080_component_slot_outlet\", () => {\n  it(\"should render slot outlet\", () => {\n    const Child = {\n      template: `<div><slot></slot></div>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      render() {\n        return h(Child, null, {\n          default: () => [\"slot content\"],\n        });\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>slot content</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/090_other_directives/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>{{ msg }} will not be compiled</span>\n  </div>\n</template>\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"50_basic_template_compiler/080_component_slot_outlet\", () => {\n  it(\"should render slot outlet\", () => {\n    const Child = {\n      template: `<div><slot></slot></div>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      render() {\n        return h(Child, null, {\n          default: () => [\"slot content\"],\n        });\n      },\n    });\n    app.mount(\"#host\");\n\n    expect(host.innerHTML).toBe(\"<div>slot content</div>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/50_basic_template_compiler/100_chore_compiler/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/.gitkeep",
    "content": ""
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/src/App.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>{{ msg }} will not be compiled</span>\n  </div>\n</template>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Handle raw text elements (script, style) - their content should not be parsed as HTML\n  if (isRawTextElement(element.tag)) {\n    const children = parseRawTextContent(context, element.tag);\n    element.children = children;\n\n    // End tag.\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End);\n    }\n\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction isRawTextElement(tag: string): boolean {\n  return tag === \"script\" || tag === \"style\";\n}\n\nfunction parseRawTextContent(context: ParserContext, tag: string): TextNode[] {\n  const start = getCursor(context);\n\n  // Find the closing tag\n  const endTagPattern = new RegExp(`</${tag}[\\\\s>]`, \"i\");\n  const match = context.source.match(endTagPattern);\n\n  if (!match || match.index === undefined) {\n    // No closing tag found, consume all remaining content\n    const content = context.source;\n    advanceBy(context, content.length);\n    return content\n      ? [\n          {\n            type: NodeTypes.TEXT,\n            content,\n            loc: getSelection(context, start),\n          },\n        ]\n      : [];\n  }\n\n  // Extract raw content up to the closing tag\n  const content = context.source.slice(0, match.index);\n  advanceBy(context, content.length);\n\n  if (!content) {\n    return [];\n  }\n\n  return [\n    {\n      type: NodeTypes.TEXT,\n      content,\n      loc: getSelection(context, start),\n    },\n  ];\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-sfc/compileScript.ts",
    "content": "import { parse as babelParse } from \"@babel/parser\";\nimport type { Statement, VariableDeclaration, FunctionDeclaration, Identifier } from \"@babel/types\";\nimport { rewriteDefault } from \"./rewriteDefault\";\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\n\nexport interface SFCScriptCompileResult {\n  code: string;\n  bindings?: Record<string, BindingTypes>;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nexport function compileScript(sfc: SFCDescriptor): SFCScriptCompileResult {\n  const { script, scriptSetup } = sfc;\n\n  // Handle script setup\n  if (scriptSetup) {\n    return compileScriptSetup(scriptSetup, script);\n  }\n\n  // Handle regular script\n  if (!script) {\n    return {\n      code: \"export default {}\",\n      bindings: {},\n    };\n  }\n\n  let code = script.content;\n\n  // Rewrite default export to __sfc__\n  code = rewriteDefault(code, \"__sfc__\");\n\n  // Add export statement\n  code += \"\\nexport default __sfc__\";\n\n  return {\n    code,\n    bindings: {},\n  };\n}\n\nfunction compileScriptSetup(\n  scriptSetup: SFCScriptBlock,\n  script: SFCScriptBlock | null,\n): SFCScriptCompileResult {\n  const bindings: Record<string, BindingTypes> = {};\n  let code = \"\";\n\n  // Parse the script setup content\n  const ast = babelParse(scriptSetup.content, {\n    sourceType: \"module\",\n    plugins: [\"typescript\"],\n  });\n\n  // Collect top-level bindings (variables, functions)\n  const setupBindings: string[] = [];\n  const imports: string[] = [];\n  const statements: string[] = [];\n\n  for (const node of ast.program.body) {\n    if (node.type === \"ImportDeclaration\") {\n      // Keep import statements\n      imports.push(scriptSetup.content.slice(node.start!, node.end!));\n      // Track imported bindings\n      for (const spec of node.specifiers) {\n        const name = spec.local.name;\n        bindings[name] = BindingTypes.SETUP_MAYBE_REF;\n      }\n    } else if (node.type === \"VariableDeclaration\") {\n      // Track variable declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      for (const decl of node.declarations) {\n        if (decl.id.type === \"Identifier\") {\n          const name = decl.id.name;\n          setupBindings.push(name);\n          if (node.kind === \"const\") {\n            bindings[name] = BindingTypes.SETUP_CONST;\n          } else {\n            bindings[name] = BindingTypes.SETUP_LET;\n          }\n        }\n      }\n    } else if (node.type === \"FunctionDeclaration\" && node.id) {\n      // Track function declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      const name = node.id.name;\n      setupBindings.push(name);\n      bindings[name] = BindingTypes.SETUP_CONST;\n    } else {\n      // Other statements\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    }\n  }\n\n  // Build the output code\n  code = imports.join(\"\\n\");\n  if (imports.length > 0) {\n    code += \"\\n\\n\";\n  }\n\n  // Handle regular script if present\n  if (script) {\n    code += rewriteDefault(script.content, \"__sfc_main__\");\n    code += \"\\n\\n\";\n  }\n\n  // Create the setup function\n  code += \"const __sfc__ = {\\n\";\n  if (script) {\n    code += \"  ...__sfc_main__,\\n\";\n  }\n  code += \"  setup() {\\n\";\n  for (const stmt of statements) {\n    code += \"    \" + stmt.split(\"\\n\").join(\"\\n    \") + \"\\n\";\n  }\n  code += \"    return { \";\n  code += setupBindings.join(\", \");\n  code += \" }\\n\";\n  code += \"  }\\n\";\n  code += \"}\\n\\n\";\n  code += \"export default __sfc__\";\n\n  return {\n    code,\n    bindings,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-sfc/compileSfc.ts",
    "content": "import { compileScript } from \"./compileScript\";\nimport { parse } from \"./parse\";\nimport * as CompilerDOM from \"../compiler-dom\";\n\nexport interface SFCCompileResult {\n  code: string;\n}\n\nexport function compileSfc(source: string, filename: string = \"anonymous.vue\"): SFCCompileResult {\n  // Parse the SFC\n  const { descriptor } = parse(source, { filename });\n\n  // Compile script\n  const scriptResult = compileScript(descriptor);\n\n  // Compile template\n  let templateCode = \"\";\n  if (descriptor.template) {\n    const templateContent = descriptor.template.content;\n    const compiledTemplate = CompilerDOM.compile(templateContent);\n    templateCode = compiledTemplate;\n  }\n\n  // Generate final code\n  let code = \"\";\n\n  // Add script code\n  code += scriptResult.code;\n\n  // Add template as render function\n  if (templateCode) {\n    code += `\\n\\n${templateCode}`;\n    code += `\\n__sfc__.render = render`;\n  }\n\n  // Add styles (inline for now)\n  if (descriptor.styles.length > 0) {\n    const styles = descriptor.styles.map((s) => s.content).join(\"\\n\");\n    code += `\\n\\n// Styles\\nconst __style__ = \\`${styles.replace(/`/g, \"\\\\`\")}\\`;`;\n    code += `\\nif (typeof document !== 'undefined') {`;\n    code += `\\n  const __styleEl__ = document.createElement('style');`;\n    code += `\\n  __styleEl__.textContent = __style__;`;\n    code += `\\n  document.head.appendChild(__styleEl__);`;\n    code += `\\n}`;\n  }\n\n  return { code };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\nexport * from \"./compileScript\";\nexport * from \"./compileSfc\";\nexport * from \"./compileTemplate\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: boolean;\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        // Check for setup attribute\n        const hasSetup = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"setup\",\n        );\n        scriptBlock.setup = hasSetup;\n        if (hasSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        } else {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\nimport { parse, compileScript, compileSfc } from \"../packages/compiler-sfc\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"60_basic_sfc_compiler/010_script_setup\", () => {\n  it(\"should parse SFC template block\", () => {\n    const source = `\n<template>\n  <div>Hello SFC!</div>\n</template>\n\n<script>\nexport default {\n  name: 'HelloWorld'\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain(\"<div>Hello SFC!</div>\");\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain(\"name: 'HelloWorld'\");\n  });\n\n  it(\"should parse SFC style block\", () => {\n    const source = `\n<template>\n  <div class=\"container\">content</div>\n</template>\n\n<style>\n.container {\n  color: red;\n}\n</style>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.styles.length).toBe(1);\n    expect(descriptor.styles[0].content).toContain(\".container\");\n    expect(descriptor.styles[0].content).toContain(\"color: red\");\n  });\n\n  it(\"should compile script block\", () => {\n    const source = `\n<template>\n  <div>Hello</div>\n</template>\n\n<script>\nexport default {\n  data() {\n    return { msg: 'hello' }\n  }\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"__sfc__\");\n    expect(result.code).toContain(\"export default __sfc__\");\n    expect(result.code).toContain(\"data()\");\n  });\n\n  it(\"should compile full SFC\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script>\nexport default {\n  data() {\n    return { msg: 'Hello from SFC!' }\n  }\n}\n</script>\n`;\n\n    const result = compileSfc(source, \"test.vue\");\n    expect(result.code).toContain(\"__sfc__\");\n    expect(result.code).toContain(\"render\");\n    expect(result.code).toContain(\"export default __sfc__\");\n  });\n\n  it(\"should handle SFC without script block\", () => {\n    const source = `\n<template>\n  <div>Static content</div>\n</template>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"export default {}\");\n  });\n\n  it(\"should include styles in compiled output\", () => {\n    const source = `\n<template>\n  <div class=\"test\">content</div>\n</template>\n\n<style>\n.test { color: blue; }\n</style>\n`;\n\n    const result = compileSfc(source, \"test.vue\");\n    expect(result.code).toContain(\"__style__\");\n    expect(result.code).toContain(\".test { color: blue; }\");\n  });\n\n  it(\"should parse script setup block\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello from script setup!'\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.scriptSetup).not.toBeNull();\n    expect(descriptor.scriptSetup?.setup).toBe(true);\n    expect(descriptor.scriptSetup?.content).toContain(\"const msg\");\n    expect(descriptor.script).toBeNull();\n  });\n\n  it(\"should compile script setup to setup function\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello!'\nconst count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup()\");\n    expect(result.code).toContain(\"const msg = 'Hello!'\");\n    expect(result.code).toContain(\"return { msg, count }\");\n  });\n\n  it(\"should handle function declarations in script setup\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup>\nfunction handleClick() {\n  console.log('clicked')\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup()\");\n    expect(result.code).toContain(\"function handleClick\");\n    expect(result.code).toContain(\"return { handleClick }\");\n  });\n\n  it(\"should preserve imports in script setup\", () => {\n    const source = `\n<template>\n  <div>{{ value }}</div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\n\nconst value = ref(0)\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"import { ref } from 'vue'\");\n    expect(result.code).toContain(\"const value = ref(0)\");\n    expect(result.code).toContain(\"return { value }\");\n  });\n\n  it(\"should track bindings from script setup\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'test'\nlet count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.bindings).toBeDefined();\n    expect(result.bindings?.msg).toBe(\"setup-const\");\n    expect(result.bindings?.count).toBe(\"setup-let\");\n  });\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/010_script_setup/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\nimport ChildComponent from './Child.vue'\n\nconst message = ref('Hello from Parent!')\nconst count = ref(42)\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>defineProps Example</h2>\n    <ChildComponent :message=\"message\" :count=\"count\" />\n    <button @click=\"count++\">Increment from parent</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/src/Child.vue",
    "content": "<script setup>\nconst props = defineProps({\n  message: String,\n  count: Number,\n})\n</script>\n\n<template>\n  <div class=\"child\">\n    <h3>Child Component</h3>\n    <p>Message: {{ props.message }}</p>\n    <p>Count: {{ props.count }}</p>\n  </div>\n</template>\n\n<style>\n.child {\n  padding: 16px;\n  background-color: #f0f0f0;\n  border-radius: 4px;\n  margin: 8px 0;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Handle raw text elements (script, style) - their content should not be parsed as HTML\n  if (isRawTextElement(element.tag)) {\n    const children = parseRawTextContent(context, element.tag);\n    element.children = children;\n\n    // End tag.\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End);\n    }\n\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction isRawTextElement(tag: string): boolean {\n  return tag === \"script\" || tag === \"style\";\n}\n\nfunction parseRawTextContent(context: ParserContext, tag: string): TextNode[] {\n  const start = getCursor(context);\n\n  // Find the closing tag\n  const endTagPattern = new RegExp(`</${tag}[\\\\s>]`, \"i\");\n  const match = context.source.match(endTagPattern);\n\n  if (!match || match.index === undefined) {\n    // No closing tag found, consume all remaining content\n    const content = context.source;\n    advanceBy(context, content.length);\n    return content\n      ? [\n          {\n            type: NodeTypes.TEXT,\n            content,\n            loc: getSelection(context, start),\n          },\n        ]\n      : [];\n  }\n\n  // Extract raw content up to the closing tag\n  const content = context.source.slice(0, match.index);\n  advanceBy(context, content.length);\n\n  if (!content) {\n    return [];\n  }\n\n  return [\n    {\n      type: NodeTypes.TEXT,\n      content,\n      loc: getSelection(context, start),\n    },\n  ];\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/compileScript.ts",
    "content": "import { parse as babelParse } from \"@babel/parser\";\nimport type {\n  Statement,\n  VariableDeclaration,\n  FunctionDeclaration,\n  Identifier,\n  CallExpression,\n  ObjectExpression,\n  ArrayExpression,\n} from \"@babel/types\";\nimport { rewriteDefault } from \"./rewriteDefault\";\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\n\nexport interface SFCScriptCompileResult {\n  code: string;\n  bindings?: Record<string, BindingTypes>;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nexport function compileScript(sfc: SFCDescriptor): SFCScriptCompileResult {\n  const { script, scriptSetup } = sfc;\n\n  // Handle script setup\n  if (scriptSetup) {\n    return compileScriptSetup(scriptSetup, script);\n  }\n\n  // Handle regular script\n  if (!script) {\n    return {\n      code: \"export default {}\",\n      bindings: {},\n    };\n  }\n\n  let code = script.content;\n\n  // Rewrite default export to __sfc__\n  code = rewriteDefault(code, \"__sfc__\");\n\n  // Add export statement\n  code += \"\\nexport default __sfc__\";\n\n  return {\n    code,\n    bindings: {},\n  };\n}\n\nfunction compileScriptSetup(\n  scriptSetup: SFCScriptBlock,\n  script: SFCScriptBlock | null,\n): SFCScriptCompileResult {\n  const bindings: Record<string, BindingTypes> = {};\n  let code = \"\";\n\n  // Parse the script setup content\n  const ast = babelParse(scriptSetup.content, {\n    sourceType: \"module\",\n    plugins: [\"typescript\"],\n  });\n\n  // Collect top-level bindings (variables, functions)\n  const setupBindings: string[] = [];\n  const imports: string[] = [];\n  const statements: string[] = [];\n  let propsDecl: string | null = null;\n  let propsRuntimeDecl: string | null = null;\n  const propNames: string[] = [];\n\n  for (const node of ast.program.body) {\n    if (node.type === \"ImportDeclaration\") {\n      // Keep import statements (but filter out defineProps import)\n      const filteredSpecifiers = node.specifiers.filter((spec) => {\n        if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n          return spec.imported.name !== \"defineProps\";\n        }\n        return true;\n      });\n      if (filteredSpecifiers.length > 0) {\n        imports.push(scriptSetup.content.slice(node.start!, node.end!));\n      }\n      // Track imported bindings\n      for (const spec of node.specifiers) {\n        const name = spec.local.name;\n        bindings[name] = BindingTypes.SETUP_MAYBE_REF;\n      }\n    } else if (node.type === \"VariableDeclaration\") {\n      // Check for defineProps call\n      const decl = node.declarations[0];\n      if (\n        decl &&\n        decl.init &&\n        decl.init.type === \"CallExpression\" &&\n        decl.init.callee.type === \"Identifier\" &&\n        decl.init.callee.name === \"defineProps\"\n      ) {\n        // Handle defineProps\n        if (decl.id.type === \"Identifier\") {\n          propsDecl = decl.id.name;\n          bindings[propsDecl] = BindingTypes.SETUP_CONST;\n        }\n        // Extract runtime props declaration\n        const arg = decl.init.arguments[0];\n        if (arg) {\n          if (arg.type === \"ArrayExpression\") {\n            // Array syntax: defineProps(['foo', 'bar'])\n            for (const el of arg.elements) {\n              if (el && el.type === \"StringLiteral\") {\n                propNames.push(el.value);\n                bindings[el.value] = BindingTypes.PROPS;\n              }\n            }\n            propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n          } else if (arg.type === \"ObjectExpression\") {\n            // Object syntax: defineProps({ foo: String, bar: Number })\n            for (const prop of arg.properties) {\n              if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                propNames.push(prop.key.name);\n                bindings[prop.key.name] = BindingTypes.PROPS;\n              }\n            }\n            propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n          }\n        }\n        continue; // Don't add to statements\n      }\n\n      // Track variable declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      for (const d of node.declarations) {\n        if (d.id.type === \"Identifier\") {\n          const name = d.id.name;\n          setupBindings.push(name);\n          if (node.kind === \"const\") {\n            bindings[name] = BindingTypes.SETUP_CONST;\n          } else {\n            bindings[name] = BindingTypes.SETUP_LET;\n          }\n        }\n      }\n    } else if (node.type === \"FunctionDeclaration\" && node.id) {\n      // Track function declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      const name = node.id.name;\n      setupBindings.push(name);\n      bindings[name] = BindingTypes.SETUP_CONST;\n    } else if (node.type === \"ExpressionStatement\") {\n      // Check for standalone defineProps call\n      if (\n        node.expression.type === \"CallExpression\" &&\n        node.expression.callee.type === \"Identifier\" &&\n        node.expression.callee.name === \"defineProps\"\n      ) {\n        const arg = node.expression.arguments[0];\n        if (arg) {\n          if (arg.type === \"ArrayExpression\") {\n            for (const el of arg.elements) {\n              if (el && el.type === \"StringLiteral\") {\n                propNames.push(el.value);\n                bindings[el.value] = BindingTypes.PROPS;\n              }\n            }\n            propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n          } else if (arg.type === \"ObjectExpression\") {\n            for (const prop of arg.properties) {\n              if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                propNames.push(prop.key.name);\n                bindings[prop.key.name] = BindingTypes.PROPS;\n              }\n            }\n            propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n          }\n        }\n        continue; // Don't add to statements\n      }\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    } else {\n      // Other statements\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    }\n  }\n\n  // Build the output code\n  code = imports.join(\"\\n\");\n  if (imports.length > 0) {\n    code += \"\\n\\n\";\n  }\n\n  // Handle regular script if present\n  if (script) {\n    code += rewriteDefault(script.content, \"__sfc_main__\");\n    code += \"\\n\\n\";\n  }\n\n  // Create the setup function\n  code += \"const __sfc__ = {\\n\";\n  if (script) {\n    code += \"  ...__sfc_main__,\\n\";\n  }\n\n  // Add props if defined\n  if (propsRuntimeDecl) {\n    code += `  props: ${propsRuntimeDecl},\\n`;\n  }\n\n  code += \"  setup(__props) {\\n\";\n\n  // Add props destructure if we have a props variable\n  if (propsDecl) {\n    code += `    const ${propsDecl} = __props\\n`;\n  }\n\n  for (const stmt of statements) {\n    code += \"    \" + stmt.split(\"\\n\").join(\"\\n    \") + \"\\n\";\n  }\n  code += \"    return { \";\n  code += setupBindings.join(\", \");\n  code += \" }\\n\";\n  code += \"  }\\n\";\n  code += \"}\\n\\n\";\n  code += \"export default __sfc__\";\n\n  return {\n    code,\n    bindings,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/compileSfc.ts",
    "content": "import { compileScript } from \"./compileScript\";\nimport { compileStyle, generateScopeId } from \"./compileStyle\";\nimport { parse } from \"./parse\";\nimport * as CompilerDOM from \"../compiler-dom\";\n\nexport interface SFCCompileResult {\n  code: string;\n  scopeId?: string;\n}\n\nexport function compileSfc(source: string, filename: string = \"anonymous.vue\"): SFCCompileResult {\n  // Parse the SFC\n  const { descriptor } = parse(source, { filename });\n\n  // Generate scope ID if we have scoped styles\n  const hasScoped = descriptor.styles.some((s) => s.scoped);\n  const scopeId = hasScoped ? generateScopeId(filename) : undefined;\n\n  // Compile script\n  const scriptResult = compileScript(descriptor);\n\n  // Compile template\n  let templateCode = \"\";\n  if (descriptor.template) {\n    const templateContent = descriptor.template.content;\n    const compiledTemplate = CompilerDOM.compile(templateContent);\n    templateCode = compiledTemplate;\n  }\n\n  // Generate final code\n  let code = \"\";\n\n  // Add script code\n  code += scriptResult.code;\n\n  // Add __scopeId if scoped\n  if (scopeId) {\n    code += `\\n__sfc__.__scopeId = \"data-v-${scopeId}\"`;\n  }\n\n  // Add template as render function\n  if (templateCode) {\n    code += `\\n\\n${templateCode}`;\n    code += `\\n__sfc__.render = render`;\n  }\n\n  // Add styles (with scoped transformation if needed)\n  if (descriptor.styles.length > 0) {\n    const compiledStyles = descriptor.styles.map((styleBlock) => {\n      const result = compileStyle({\n        source: styleBlock.content,\n        id: scopeId || \"\",\n        scoped: styleBlock.scoped,\n      });\n      return result.code;\n    });\n\n    const styles = compiledStyles.join(\"\\n\");\n    code += `\\n\\n// Styles\\nconst __style__ = \\`${styles.replace(/`/g, \"\\\\`\")}\\`;`;\n    code += `\\nif (typeof document !== 'undefined') {`;\n    code += `\\n  const __styleEl__ = document.createElement('style');`;\n    code += `\\n  __styleEl__.textContent = __style__;`;\n    code += `\\n  document.head.appendChild(__styleEl__);`;\n    code += `\\n}`;\n  }\n\n  return { code, scopeId };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/compileStyle.ts",
    "content": "import type { SFCStyleBlock } from \"./parse\";\n\nexport interface SFCStyleCompileOptions {\n  source: string;\n  id: string;\n  scoped?: boolean;\n}\n\nexport interface SFCStyleCompileResult {\n  code: string;\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): SFCStyleCompileResult {\n  const { source, id, scoped } = options;\n\n  if (!scoped) {\n    return { code: source };\n  }\n\n  // Apply scoped transformation\n  const scopedCode = applyScopedCss(source, id);\n\n  return { code: scopedCode };\n}\n\nfunction applyScopedCss(css: string, scopeId: string): string {\n  // Simple scoped CSS implementation\n  // Add the scope attribute selector to each rule\n  const scopeAttr = `[data-v-${scopeId}]`;\n\n  // Match CSS rule selectors (simplified)\n  // This handles basic cases like .class, #id, element, etc.\n  return css.replace(/([^\\{\\}]+)\\{/g, (match, selectors: string) => {\n    const scopedSelectors = selectors\n      .split(\",\")\n      .map((sel: string) => {\n        sel = sel.trim();\n        if (!sel) return sel;\n\n        // Handle :deep() pseudo-selector\n        if (sel.includes(\":deep(\")) {\n          return sel.replace(/:deep\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Handle :global() pseudo-selector\n        if (sel.includes(\":global(\")) {\n          return sel.replace(/:global\\(([^)]+)\\)/g, \"$1\");\n        }\n\n        // Handle :slotted() pseudo-selector\n        if (sel.includes(\":slotted(\")) {\n          return sel.replace(/:slotted\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Add scope to regular selectors\n        // For compound selectors, add scope to the last element\n        const parts = sel.split(/\\s+/);\n        if (parts.length > 0) {\n          const lastPart = parts[parts.length - 1];\n          // Handle pseudo-elements and pseudo-classes\n          if (lastPart.includes(\"::\") || lastPart.match(/:[a-z-]+$/)) {\n            const [base, ...rest] = lastPart.split(/(::?[a-z-]+.*)/);\n            parts[parts.length - 1] = base + scopeAttr + rest.join(\"\");\n          } else {\n            parts[parts.length - 1] = lastPart + scopeAttr;\n          }\n        }\n        return parts.join(\" \");\n      })\n      .join(\", \");\n\n    return scopedSelectors + \" {\";\n  });\n}\n\nexport function generateScopeId(filename: string): string {\n  // Simple hash function for generating scope ID\n  let hash = 0;\n  for (let i = 0; i < filename.length; i++) {\n    const char = filename.charCodeAt(i);\n    hash = (hash << 5) - hash + char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n  return Math.abs(hash).toString(16).slice(0, 8);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\nexport * from \"./compileScript\";\nexport * from \"./compileStyle\";\nexport * from \"./compileSfc\";\nexport * from \"./compileTemplate\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: boolean;\n}\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n  scoped?: boolean;\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        // Check for setup attribute\n        const hasSetup = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"setup\",\n        );\n        scriptBlock.setup = hasSetup;\n        if (hasSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        } else {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        const styleBlock = createBlock(node, source) as SFCStyleBlock;\n        // Check for scoped attribute\n        const hasScoped = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"scoped\",\n        );\n        styleBlock.scoped = hasScoped;\n        descriptor.styles.push(styleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\nimport { parse, compileScript, compileSfc } from \"../packages/compiler-sfc\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"60_basic_sfc_compiler/010_script_setup\", () => {\n  it(\"should parse SFC template block\", () => {\n    const source = `\n<template>\n  <div>Hello SFC!</div>\n</template>\n\n<script>\nexport default {\n  name: 'HelloWorld'\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain(\"<div>Hello SFC!</div>\");\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain(\"name: 'HelloWorld'\");\n  });\n\n  it(\"should compile script setup to setup function\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello!'\nconst count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup(__props)\");\n    expect(result.code).toContain(\"const msg = 'Hello!'\");\n    expect(result.code).toContain(\"return { msg, count }\");\n  });\n\n  it(\"should track bindings from script setup\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'test'\nlet count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.bindings).toBeDefined();\n    expect(result.bindings?.msg).toBe(\"setup-const\");\n    expect(result.bindings?.count).toBe(\"setup-let\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/020_define_props\", () => {\n  it(\"should compile defineProps with array syntax\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst props = defineProps(['msg', 'count'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['msg', 'count']\");\n    expect(result.code).toContain(\"const props = __props\");\n    expect(result.bindings?.msg).toBe(\"props\");\n    expect(result.bindings?.count).toBe(\"props\");\n  });\n\n  it(\"should compile defineProps with object syntax\", () => {\n    const source = `\n<template>\n  <div>{{ title }}</div>\n</template>\n\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props:\");\n    expect(result.code).toContain(\"title: String\");\n    expect(result.code).toContain(\"count: Number\");\n    expect(result.bindings?.title).toBe(\"props\");\n    expect(result.bindings?.count).toBe(\"props\");\n  });\n\n  it(\"should compile standalone defineProps call\", () => {\n    const source = `\n<template>\n  <div>content</div>\n</template>\n\n<script setup>\ndefineProps(['message'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['message']\");\n    expect(result.bindings?.message).toBe(\"props\");\n  });\n\n  it(\"should work with other bindings\", () => {\n    const source = `\n<template>\n  <div>{{ msg }} - {{ count }}</div>\n</template>\n\n<script setup>\nconst props = defineProps(['msg'])\nconst count = 0\n\nfunction handleClick() {\n  console.log('clicked')\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['msg']\");\n    expect(result.code).toContain(\"const count = 0\");\n    expect(result.code).toContain(\"function handleClick\");\n    expect(result.code).toContain(\"return { count, handleClick }\");\n    expect(result.bindings?.msg).toBe(\"props\");\n    expect(result.bindings?.count).toBe(\"setup-const\");\n    expect(result.bindings?.handleClick).toBe(\"setup-const\");\n  });\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/020_define_props/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\nimport ChildComponent from './Child.vue'\n\nconst count = ref(0)\nconst logs = ref([])\n\nconst handleIncrement = () => {\n  count.value++\n  logs.value.push(`increment event received`)\n}\n\nconst handleCustomEvent = (payload) => {\n  logs.value.push(`custom event: ${payload}`)\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>defineEmits Example</h2>\n    <p>Parent count: {{ count }}</p>\n    <ChildComponent @increment=\"handleIncrement\" @custom-event=\"handleCustomEvent\" />\n    <h3>Event Logs:</h3>\n    <ul>\n      <li v-for=\"(log, i) in logs\" :key=\"i\">{{ log }}</li>\n    </ul>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/src/Child.vue",
    "content": "<script setup>\nconst emit = defineEmits(['increment', 'custom-event'])\n\nconst handleClick = () => {\n  emit('increment')\n}\n\nconst sendCustomEvent = () => {\n  emit('custom-event', 'Hello from child!')\n}\n</script>\n\n<template>\n  <div class=\"child\">\n    <h3>Child Component</h3>\n    <button @click=\"handleClick\">Emit increment</button>\n    <button @click=\"sendCustomEvent\">Emit custom event</button>\n  </div>\n</template>\n\n<style>\n.child {\n  padding: 16px;\n  background-color: #f0f0f0;\n  border-radius: 4px;\n  margin: 8px 0;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Handle raw text elements (script, style) - their content should not be parsed as HTML\n  if (isRawTextElement(element.tag)) {\n    const children = parseRawTextContent(context, element.tag);\n    element.children = children;\n\n    // End tag.\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End);\n    }\n\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction isRawTextElement(tag: string): boolean {\n  return tag === \"script\" || tag === \"style\";\n}\n\nfunction parseRawTextContent(context: ParserContext, tag: string): TextNode[] {\n  const start = getCursor(context);\n\n  // Find the closing tag\n  const endTagPattern = new RegExp(`</${tag}[\\\\s>]`, \"i\");\n  const match = context.source.match(endTagPattern);\n\n  if (!match || match.index === undefined) {\n    // No closing tag found, consume all remaining content\n    const content = context.source;\n    advanceBy(context, content.length);\n    return content\n      ? [\n          {\n            type: NodeTypes.TEXT,\n            content,\n            loc: getSelection(context, start),\n          },\n        ]\n      : [];\n  }\n\n  // Extract raw content up to the closing tag\n  const content = context.source.slice(0, match.index);\n  advanceBy(context, content.length);\n\n  if (!content) {\n    return [];\n  }\n\n  return [\n    {\n      type: NodeTypes.TEXT,\n      content,\n      loc: getSelection(context, start),\n    },\n  ];\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/compileScript.ts",
    "content": "import { parse as babelParse } from \"@babel/parser\";\nimport type {\n  Statement,\n  VariableDeclaration,\n  FunctionDeclaration,\n  Identifier,\n  CallExpression,\n  ObjectExpression,\n  ArrayExpression,\n} from \"@babel/types\";\nimport { rewriteDefault } from \"./rewriteDefault\";\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\n\nexport interface SFCScriptCompileResult {\n  code: string;\n  bindings?: Record<string, BindingTypes>;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nconst DEFINE_PROPS = \"defineProps\";\nconst DEFINE_EMITS = \"defineEmits\";\n\nexport function compileScript(sfc: SFCDescriptor): SFCScriptCompileResult {\n  const { script, scriptSetup } = sfc;\n\n  // Handle script setup\n  if (scriptSetup) {\n    return compileScriptSetup(scriptSetup, script);\n  }\n\n  // Handle regular script\n  if (!script) {\n    return {\n      code: \"export default {}\",\n      bindings: {},\n    };\n  }\n\n  let code = script.content;\n\n  // Rewrite default export to __sfc__\n  code = rewriteDefault(code, \"__sfc__\");\n\n  // Add export statement\n  code += \"\\nexport default __sfc__\";\n\n  return {\n    code,\n    bindings: {},\n  };\n}\n\nfunction compileScriptSetup(\n  scriptSetup: SFCScriptBlock,\n  script: SFCScriptBlock | null,\n): SFCScriptCompileResult {\n  const bindings: Record<string, BindingTypes> = {};\n  let code = \"\";\n\n  // Parse the script setup content\n  const ast = babelParse(scriptSetup.content, {\n    sourceType: \"module\",\n    plugins: [\"typescript\"],\n  });\n\n  // Collect top-level bindings (variables, functions)\n  const setupBindings: string[] = [];\n  const imports: string[] = [];\n  const statements: string[] = [];\n  let propsDecl: string | null = null;\n  let propsRuntimeDecl: string | null = null;\n  let emitsDecl: string | null = null;\n  let emitsRuntimeDecl: string | null = null;\n  const propNames: string[] = [];\n  const emitNames: string[] = [];\n\n  for (const node of ast.program.body) {\n    if (node.type === \"ImportDeclaration\") {\n      // Keep import statements (but filter out compiler macros)\n      const filteredSpecifiers = node.specifiers.filter((spec) => {\n        if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n          return ![DEFINE_PROPS, DEFINE_EMITS].includes(spec.imported.name);\n        }\n        return true;\n      });\n      if (filteredSpecifiers.length > 0) {\n        imports.push(scriptSetup.content.slice(node.start!, node.end!));\n      }\n      // Track imported bindings\n      for (const spec of node.specifiers) {\n        const name = spec.local.name;\n        bindings[name] = BindingTypes.SETUP_MAYBE_REF;\n      }\n    } else if (node.type === \"VariableDeclaration\") {\n      const decl = node.declarations[0];\n      if (\n        decl &&\n        decl.init &&\n        decl.init.type === \"CallExpression\" &&\n        decl.init.callee.type === \"Identifier\"\n      ) {\n        const calleeName = decl.init.callee.name;\n\n        // Check for defineProps call\n        if (calleeName === DEFINE_PROPS) {\n          if (decl.id.type === \"Identifier\") {\n            propsDecl = decl.id.name;\n            bindings[propsDecl] = BindingTypes.SETUP_CONST;\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        // Check for defineEmits call\n        if (calleeName === DEFINE_EMITS) {\n          if (decl.id.type === \"Identifier\") {\n            emitsDecl = decl.id.name;\n            bindings[emitsDecl] = BindingTypes.SETUP_CONST;\n            setupBindings.push(emitsDecl);\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n\n      // Track regular variable declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      for (const d of node.declarations) {\n        if (d.id.type === \"Identifier\") {\n          const name = d.id.name;\n          setupBindings.push(name);\n          if (node.kind === \"const\") {\n            bindings[name] = BindingTypes.SETUP_CONST;\n          } else {\n            bindings[name] = BindingTypes.SETUP_LET;\n          }\n        }\n      }\n    } else if (node.type === \"FunctionDeclaration\" && node.id) {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      const name = node.id.name;\n      setupBindings.push(name);\n      bindings[name] = BindingTypes.SETUP_CONST;\n    } else if (node.type === \"ExpressionStatement\") {\n      // Check for standalone defineProps/defineEmits call\n      if (\n        node.expression.type === \"CallExpression\" &&\n        node.expression.callee.type === \"Identifier\"\n      ) {\n        const calleeName = node.expression.callee.name;\n\n        if (calleeName === DEFINE_PROPS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        if (calleeName === DEFINE_EMITS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    } else {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    }\n  }\n\n  // Build the output code\n  code = imports.join(\"\\n\");\n  if (imports.length > 0) {\n    code += \"\\n\\n\";\n  }\n\n  // Handle regular script if present\n  if (script) {\n    code += rewriteDefault(script.content, \"__sfc_main__\");\n    code += \"\\n\\n\";\n  }\n\n  // Create the setup function\n  code += \"const __sfc__ = {\\n\";\n  if (script) {\n    code += \"  ...__sfc_main__,\\n\";\n  }\n\n  // Add props if defined\n  if (propsRuntimeDecl) {\n    code += `  props: ${propsRuntimeDecl},\\n`;\n  }\n\n  // Add emits if defined\n  if (emitsRuntimeDecl) {\n    code += `  emits: ${emitsRuntimeDecl},\\n`;\n  }\n\n  code += \"  setup(__props, { emit: __emit }) {\\n\";\n\n  // Add props destructure if we have a props variable\n  if (propsDecl) {\n    code += `    const ${propsDecl} = __props\\n`;\n  }\n\n  // Add emit function if we have an emit variable\n  if (emitsDecl) {\n    code += `    const ${emitsDecl} = __emit\\n`;\n  }\n\n  for (const stmt of statements) {\n    code += \"    \" + stmt.split(\"\\n\").join(\"\\n    \") + \"\\n\";\n  }\n  code += \"    return { \";\n  code += setupBindings.join(\", \");\n  code += \" }\\n\";\n  code += \"  }\\n\";\n  code += \"}\\n\\n\";\n  code += \"export default __sfc__\";\n\n  return {\n    code,\n    bindings,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/compileSfc.ts",
    "content": "import { compileScript } from \"./compileScript\";\nimport { compileStyle, generateScopeId } from \"./compileStyle\";\nimport { parse } from \"./parse\";\nimport * as CompilerDOM from \"../compiler-dom\";\n\nexport interface SFCCompileResult {\n  code: string;\n  scopeId?: string;\n}\n\nexport function compileSfc(source: string, filename: string = \"anonymous.vue\"): SFCCompileResult {\n  // Parse the SFC\n  const { descriptor } = parse(source, { filename });\n\n  // Generate scope ID if we have scoped styles\n  const hasScoped = descriptor.styles.some((s) => s.scoped);\n  const scopeId = hasScoped ? generateScopeId(filename) : undefined;\n\n  // Compile script\n  const scriptResult = compileScript(descriptor);\n\n  // Compile template\n  let templateCode = \"\";\n  if (descriptor.template) {\n    const templateContent = descriptor.template.content;\n    const compiledTemplate = CompilerDOM.compile(templateContent);\n    templateCode = compiledTemplate;\n  }\n\n  // Generate final code\n  let code = \"\";\n\n  // Add script code\n  code += scriptResult.code;\n\n  // Add __scopeId if scoped\n  if (scopeId) {\n    code += `\\n__sfc__.__scopeId = \"data-v-${scopeId}\"`;\n  }\n\n  // Add template as render function\n  if (templateCode) {\n    code += `\\n\\n${templateCode}`;\n    code += `\\n__sfc__.render = render`;\n  }\n\n  // Add styles (with scoped transformation if needed)\n  if (descriptor.styles.length > 0) {\n    const compiledStyles = descriptor.styles.map((styleBlock) => {\n      const result = compileStyle({\n        source: styleBlock.content,\n        id: scopeId || \"\",\n        scoped: styleBlock.scoped,\n      });\n      return result.code;\n    });\n\n    const styles = compiledStyles.join(\"\\n\");\n    code += `\\n\\n// Styles\\nconst __style__ = \\`${styles.replace(/`/g, \"\\\\`\")}\\`;`;\n    code += `\\nif (typeof document !== 'undefined') {`;\n    code += `\\n  const __styleEl__ = document.createElement('style');`;\n    code += `\\n  __styleEl__.textContent = __style__;`;\n    code += `\\n  document.head.appendChild(__styleEl__);`;\n    code += `\\n}`;\n  }\n\n  return { code, scopeId };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/compileStyle.ts",
    "content": "import type { SFCStyleBlock } from \"./parse\";\n\nexport interface SFCStyleCompileOptions {\n  source: string;\n  id: string;\n  scoped?: boolean;\n}\n\nexport interface SFCStyleCompileResult {\n  code: string;\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): SFCStyleCompileResult {\n  const { source, id, scoped } = options;\n\n  if (!scoped) {\n    return { code: source };\n  }\n\n  // Apply scoped transformation\n  const scopedCode = applyScopedCss(source, id);\n\n  return { code: scopedCode };\n}\n\nfunction applyScopedCss(css: string, scopeId: string): string {\n  // Simple scoped CSS implementation\n  // Add the scope attribute selector to each rule\n  const scopeAttr = `[data-v-${scopeId}]`;\n\n  // Match CSS rule selectors (simplified)\n  // This handles basic cases like .class, #id, element, etc.\n  return css.replace(/([^\\{\\}]+)\\{/g, (match, selectors: string) => {\n    const scopedSelectors = selectors\n      .split(\",\")\n      .map((sel: string) => {\n        sel = sel.trim();\n        if (!sel) return sel;\n\n        // Handle :deep() pseudo-selector\n        if (sel.includes(\":deep(\")) {\n          return sel.replace(/:deep\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Handle :global() pseudo-selector\n        if (sel.includes(\":global(\")) {\n          return sel.replace(/:global\\(([^)]+)\\)/g, \"$1\");\n        }\n\n        // Handle :slotted() pseudo-selector\n        if (sel.includes(\":slotted(\")) {\n          return sel.replace(/:slotted\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Add scope to regular selectors\n        // For compound selectors, add scope to the last element\n        const parts = sel.split(/\\s+/);\n        if (parts.length > 0) {\n          const lastPart = parts[parts.length - 1];\n          // Handle pseudo-elements and pseudo-classes\n          if (lastPart.includes(\"::\") || lastPart.match(/:[a-z-]+$/)) {\n            const [base, ...rest] = lastPart.split(/(::?[a-z-]+.*)/);\n            parts[parts.length - 1] = base + scopeAttr + rest.join(\"\");\n          } else {\n            parts[parts.length - 1] = lastPart + scopeAttr;\n          }\n        }\n        return parts.join(\" \");\n      })\n      .join(\", \");\n\n    return scopedSelectors + \" {\";\n  });\n}\n\nexport function generateScopeId(filename: string): string {\n  // Simple hash function for generating scope ID\n  let hash = 0;\n  for (let i = 0; i < filename.length; i++) {\n    const char = filename.charCodeAt(i);\n    hash = (hash << 5) - hash + char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n  return Math.abs(hash).toString(16).slice(0, 8);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\nexport * from \"./compileScript\";\nexport * from \"./compileStyle\";\nexport * from \"./compileSfc\";\nexport * from \"./compileTemplate\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: boolean;\n}\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n  scoped?: boolean;\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        // Check for setup attribute\n        const hasSetup = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"setup\",\n        );\n        scriptBlock.setup = hasSetup;\n        if (hasSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        } else {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        const styleBlock = createBlock(node, source) as SFCStyleBlock;\n        // Check for scoped attribute\n        const hasScoped = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"scoped\",\n        );\n        styleBlock.scoped = hasScoped;\n        descriptor.styles.push(styleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\nimport { parse, compileScript, compileSfc } from \"../packages/compiler-sfc\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"60_basic_sfc_compiler/010_script_setup\", () => {\n  it(\"should parse SFC template block\", () => {\n    const source = `\n<template>\n  <div>Hello SFC!</div>\n</template>\n\n<script>\nexport default {\n  name: 'HelloWorld'\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain(\"<div>Hello SFC!</div>\");\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain(\"name: 'HelloWorld'\");\n  });\n\n  it(\"should compile script setup to setup function\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello!'\nconst count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup(__props, { emit: __emit })\");\n    expect(result.code).toContain(\"const msg = 'Hello!'\");\n    expect(result.code).toContain(\"return { msg, count }\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/020_define_props\", () => {\n  it(\"should compile defineProps with array syntax\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst props = defineProps(['msg', 'count'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['msg', 'count']\");\n    expect(result.code).toContain(\"const props = __props\");\n    expect(result.bindings?.msg).toBe(\"props\");\n    expect(result.bindings?.count).toBe(\"props\");\n  });\n\n  it(\"should compile defineProps with object syntax\", () => {\n    const source = `\n<template>\n  <div>{{ title }}</div>\n</template>\n\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props:\");\n    expect(result.code).toContain(\"title: String\");\n    expect(result.code).toContain(\"count: Number\");\n    expect(result.bindings?.title).toBe(\"props\");\n    expect(result.bindings?.count).toBe(\"props\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/030_define_emits\", () => {\n  it(\"should compile defineEmits with array syntax\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup>\nconst emit = defineEmits(['click', 'update'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits: ['click', 'update']\");\n    expect(result.code).toContain(\"const emit = __emit\");\n  });\n\n  it(\"should compile defineEmits with object syntax\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup>\nconst emit = defineEmits({\n  click: null,\n  update: (value) => typeof value === 'string'\n})\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits:\");\n    expect(result.code).toContain(\"click: null\");\n    expect(result.code).toContain(\"const emit = __emit\");\n  });\n\n  it(\"should compile standalone defineEmits call\", () => {\n    const source = `\n<template>\n  <button>Click</button>\n</template>\n\n<script setup>\ndefineEmits(['submit'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits: ['submit']\");\n  });\n\n  it(\"should work with defineProps and defineEmits together\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">{{ label }}</button>\n</template>\n\n<script setup>\nconst props = defineProps(['label'])\nconst emit = defineEmits(['click'])\n\nfunction handleClick() {\n  emit('click')\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['label']\");\n    expect(result.code).toContain(\"emits: ['click']\");\n    expect(result.code).toContain(\"const props = __props\");\n    expect(result.code).toContain(\"const emit = __emit\");\n    expect(result.code).toContain(\"function handleClick\");\n    expect(result.bindings?.label).toBe(\"props\");\n    expect(result.bindings?.emit).toBe(\"setup-const\");\n    expect(result.bindings?.handleClick).toBe(\"setup-const\");\n  });\n\n  it(\"should include emit in setup context\", () => {\n    const source = `\n<template>\n  <div>content</div>\n</template>\n\n<script setup>\nconst emit = defineEmits(['change'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup(__props, { emit: __emit })\");\n  });\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/030_define_emits/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\nimport ChildComponent from './Child.vue'\n\nconst message = ref('Hello from App!')\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Scoped CSS Example</h2>\n    <p class=\"styled-text\">{{ message }}</p>\n    <p class=\"red-text\">This text is styled with scoped CSS (red)</p>\n    <ChildComponent />\n  </div>\n</template>\n\n<style scoped>\n.container {\n  padding: 16px;\n  border: 2px solid #42b883;\n}\n.styled-text {\n  color: #42b883;\n  font-weight: bold;\n}\n.red-text {\n  color: red;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/src/Child.vue",
    "content": "<script setup>\n</script>\n\n<template>\n  <div class=\"child\">\n    <h3>Child Component</h3>\n    <p class=\"styled-text\">This text uses the same class but is in a different component</p>\n    <p class=\"red-text\">This red-text class is defined in parent's scoped CSS</p>\n  </div>\n</template>\n\n<style scoped>\n.child {\n  padding: 16px;\n  background-color: #e0e0e0;\n  border-radius: 4px;\n  margin: 8px 0;\n}\n.styled-text {\n  color: blue;\n  font-style: italic;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Handle raw text elements (script, style) - their content should not be parsed as HTML\n  if (isRawTextElement(element.tag)) {\n    const children = parseRawTextContent(context, element.tag);\n    element.children = children;\n\n    // End tag.\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End);\n    }\n\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction isRawTextElement(tag: string): boolean {\n  return tag === \"script\" || tag === \"style\";\n}\n\nfunction parseRawTextContent(context: ParserContext, tag: string): TextNode[] {\n  const start = getCursor(context);\n\n  // Find the closing tag\n  const endTagPattern = new RegExp(`</${tag}[\\\\s>]`, \"i\");\n  const match = context.source.match(endTagPattern);\n\n  if (!match || match.index === undefined) {\n    // No closing tag found, consume all remaining content\n    const content = context.source;\n    advanceBy(context, content.length);\n    return content\n      ? [\n          {\n            type: NodeTypes.TEXT,\n            content,\n            loc: getSelection(context, start),\n          },\n        ]\n      : [];\n  }\n\n  // Extract raw content up to the closing tag\n  const content = context.source.slice(0, match.index);\n  advanceBy(context, content.length);\n\n  if (!content) {\n    return [];\n  }\n\n  return [\n    {\n      type: NodeTypes.TEXT,\n      content,\n      loc: getSelection(context, start),\n    },\n  ];\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/compileScript.ts",
    "content": "import { parse as babelParse } from \"@babel/parser\";\nimport type {\n  Statement,\n  VariableDeclaration,\n  FunctionDeclaration,\n  Identifier,\n  CallExpression,\n  ObjectExpression,\n  ArrayExpression,\n} from \"@babel/types\";\nimport { rewriteDefault } from \"./rewriteDefault\";\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\n\nexport interface SFCScriptCompileResult {\n  code: string;\n  bindings?: Record<string, BindingTypes>;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nconst DEFINE_PROPS = \"defineProps\";\nconst DEFINE_EMITS = \"defineEmits\";\n\nexport function compileScript(sfc: SFCDescriptor): SFCScriptCompileResult {\n  const { script, scriptSetup } = sfc;\n\n  // Handle script setup\n  if (scriptSetup) {\n    return compileScriptSetup(scriptSetup, script);\n  }\n\n  // Handle regular script\n  if (!script) {\n    return {\n      code: \"export default {}\",\n      bindings: {},\n    };\n  }\n\n  let code = script.content;\n\n  // Rewrite default export to __sfc__\n  code = rewriteDefault(code, \"__sfc__\");\n\n  // Add export statement\n  code += \"\\nexport default __sfc__\";\n\n  return {\n    code,\n    bindings: {},\n  };\n}\n\nfunction compileScriptSetup(\n  scriptSetup: SFCScriptBlock,\n  script: SFCScriptBlock | null,\n): SFCScriptCompileResult {\n  const bindings: Record<string, BindingTypes> = {};\n  let code = \"\";\n\n  // Parse the script setup content\n  const ast = babelParse(scriptSetup.content, {\n    sourceType: \"module\",\n    plugins: [\"typescript\"],\n  });\n\n  // Collect top-level bindings (variables, functions)\n  const setupBindings: string[] = [];\n  const imports: string[] = [];\n  const statements: string[] = [];\n  let propsDecl: string | null = null;\n  let propsRuntimeDecl: string | null = null;\n  let emitsDecl: string | null = null;\n  let emitsRuntimeDecl: string | null = null;\n  const propNames: string[] = [];\n  const emitNames: string[] = [];\n\n  for (const node of ast.program.body) {\n    if (node.type === \"ImportDeclaration\") {\n      // Keep import statements (but filter out compiler macros)\n      const filteredSpecifiers = node.specifiers.filter((spec) => {\n        if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n          return ![DEFINE_PROPS, DEFINE_EMITS].includes(spec.imported.name);\n        }\n        return true;\n      });\n      if (filteredSpecifiers.length > 0) {\n        imports.push(scriptSetup.content.slice(node.start!, node.end!));\n      }\n      // Track imported bindings\n      for (const spec of node.specifiers) {\n        const name = spec.local.name;\n        bindings[name] = BindingTypes.SETUP_MAYBE_REF;\n      }\n    } else if (node.type === \"VariableDeclaration\") {\n      const decl = node.declarations[0];\n      if (\n        decl &&\n        decl.init &&\n        decl.init.type === \"CallExpression\" &&\n        decl.init.callee.type === \"Identifier\"\n      ) {\n        const calleeName = decl.init.callee.name;\n\n        // Check for defineProps call\n        if (calleeName === DEFINE_PROPS) {\n          if (decl.id.type === \"Identifier\") {\n            propsDecl = decl.id.name;\n            bindings[propsDecl] = BindingTypes.SETUP_CONST;\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        // Check for defineEmits call\n        if (calleeName === DEFINE_EMITS) {\n          if (decl.id.type === \"Identifier\") {\n            emitsDecl = decl.id.name;\n            bindings[emitsDecl] = BindingTypes.SETUP_CONST;\n            setupBindings.push(emitsDecl);\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n\n      // Track regular variable declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      for (const d of node.declarations) {\n        if (d.id.type === \"Identifier\") {\n          const name = d.id.name;\n          setupBindings.push(name);\n          if (node.kind === \"const\") {\n            bindings[name] = BindingTypes.SETUP_CONST;\n          } else {\n            bindings[name] = BindingTypes.SETUP_LET;\n          }\n        }\n      }\n    } else if (node.type === \"FunctionDeclaration\" && node.id) {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      const name = node.id.name;\n      setupBindings.push(name);\n      bindings[name] = BindingTypes.SETUP_CONST;\n    } else if (node.type === \"ExpressionStatement\") {\n      // Check for standalone defineProps/defineEmits call\n      if (\n        node.expression.type === \"CallExpression\" &&\n        node.expression.callee.type === \"Identifier\"\n      ) {\n        const calleeName = node.expression.callee.name;\n\n        if (calleeName === DEFINE_PROPS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        if (calleeName === DEFINE_EMITS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    } else {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    }\n  }\n\n  // Build the output code\n  code = imports.join(\"\\n\");\n  if (imports.length > 0) {\n    code += \"\\n\\n\";\n  }\n\n  // Handle regular script if present\n  if (script) {\n    code += rewriteDefault(script.content, \"__sfc_main__\");\n    code += \"\\n\\n\";\n  }\n\n  // Create the setup function\n  code += \"const __sfc__ = {\\n\";\n  if (script) {\n    code += \"  ...__sfc_main__,\\n\";\n  }\n\n  // Add props if defined\n  if (propsRuntimeDecl) {\n    code += `  props: ${propsRuntimeDecl},\\n`;\n  }\n\n  // Add emits if defined\n  if (emitsRuntimeDecl) {\n    code += `  emits: ${emitsRuntimeDecl},\\n`;\n  }\n\n  code += \"  setup(__props, { emit: __emit }) {\\n\";\n\n  // Add props destructure if we have a props variable\n  if (propsDecl) {\n    code += `    const ${propsDecl} = __props\\n`;\n  }\n\n  // Add emit function if we have an emit variable\n  if (emitsDecl) {\n    code += `    const ${emitsDecl} = __emit\\n`;\n  }\n\n  for (const stmt of statements) {\n    code += \"    \" + stmt.split(\"\\n\").join(\"\\n    \") + \"\\n\";\n  }\n  code += \"    return { \";\n  code += setupBindings.join(\", \");\n  code += \" }\\n\";\n  code += \"  }\\n\";\n  code += \"}\\n\\n\";\n  code += \"export default __sfc__\";\n\n  return {\n    code,\n    bindings,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/compileSfc.ts",
    "content": "import { compileScript } from \"./compileScript\";\nimport { compileStyle, generateScopeId } from \"./compileStyle\";\nimport { parse } from \"./parse\";\nimport * as CompilerDOM from \"../compiler-dom\";\n\nexport interface SFCCompileResult {\n  code: string;\n  scopeId?: string;\n}\n\nexport function compileSfc(source: string, filename: string = \"anonymous.vue\"): SFCCompileResult {\n  // Parse the SFC\n  const { descriptor } = parse(source, { filename });\n\n  // Generate scope ID if we have scoped styles\n  const hasScoped = descriptor.styles.some((s) => s.scoped);\n  const scopeId = hasScoped ? generateScopeId(filename) : undefined;\n\n  // Compile script\n  const scriptResult = compileScript(descriptor);\n\n  // Compile template\n  let templateCode = \"\";\n  if (descriptor.template) {\n    const templateContent = descriptor.template.content;\n    const compiledTemplate = CompilerDOM.compile(templateContent);\n    templateCode = compiledTemplate;\n  }\n\n  // Generate final code\n  let code = \"\";\n\n  // Add script code\n  code += scriptResult.code;\n\n  // Add __scopeId if scoped\n  if (scopeId) {\n    code += `\\n__sfc__.__scopeId = \"data-v-${scopeId}\"`;\n  }\n\n  // Add template as render function\n  if (templateCode) {\n    code += `\\n\\n${templateCode}`;\n    code += `\\n__sfc__.render = render`;\n  }\n\n  // Add styles (with scoped transformation if needed)\n  if (descriptor.styles.length > 0) {\n    const compiledStyles = descriptor.styles.map((styleBlock) => {\n      const result = compileStyle({\n        source: styleBlock.content,\n        id: scopeId || \"\",\n        scoped: styleBlock.scoped,\n      });\n      return result.code;\n    });\n\n    const styles = compiledStyles.join(\"\\n\");\n    code += `\\n\\n// Styles\\nconst __style__ = \\`${styles.replace(/`/g, \"\\\\`\")}\\`;`;\n    code += `\\nif (typeof document !== 'undefined') {`;\n    code += `\\n  const __styleEl__ = document.createElement('style');`;\n    code += `\\n  __styleEl__.textContent = __style__;`;\n    code += `\\n  document.head.appendChild(__styleEl__);`;\n    code += `\\n}`;\n  }\n\n  return { code, scopeId };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/compileStyle.ts",
    "content": "import type { SFCStyleBlock } from \"./parse\";\n\nexport interface SFCStyleCompileOptions {\n  source: string;\n  id: string;\n  scoped?: boolean;\n}\n\nexport interface SFCStyleCompileResult {\n  code: string;\n  /** CSS variable names extracted from v-bind() expressions */\n  cssVars?: string[];\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): SFCStyleCompileResult {\n  const { source, id, scoped } = options;\n\n  // First, process v-bind() expressions\n  const { code: processedCss, cssVars } = processVBind(source, id);\n\n  if (!scoped) {\n    return { code: processedCss, cssVars };\n  }\n\n  // Apply scoped transformation\n  const scopedCode = applyScopedCss(processedCss, id);\n\n  return { code: scopedCode, cssVars };\n}\n\n/**\n * Process v-bind() expressions in CSS\n *\n * Converts v-bind(expr) to var(--{id}-{expr})\n *\n * NOTE: v-bind() in CSS has performance implications:\n * - Each v-bind() creates a CSS custom property that is set via inline style\n * - The values are updated reactively when the expression changes\n * - This triggers style recalculation on every update\n * - For frequently changing values, consider using inline styles instead\n */\nfunction processVBind(css: string, id: string): { code: string; cssVars: string[] } {\n  const cssVars: string[] = [];\n  const vBindRE = /v-bind\\s*\\(\\s*([^)]+)\\s*\\)/g;\n\n  const code = css.replace(vBindRE, (match, expr: string) => {\n    // Normalize the expression (remove quotes if present)\n    let varName = expr.trim();\n    if (\n      (varName.startsWith(\"'\") && varName.endsWith(\"'\")) ||\n      (varName.startsWith('\"') && varName.endsWith('\"'))\n    ) {\n      varName = varName.slice(1, -1);\n    }\n\n    // Escape special characters for CSS variable names\n    const escapedVarName = escapeCssVarName(varName);\n\n    if (!cssVars.includes(varName)) {\n      cssVars.push(varName);\n    }\n\n    return `var(--${id}-${escapedVarName})`;\n  });\n\n  return { code, cssVars };\n}\n\n/**\n * Escape special characters in CSS variable names\n * CSS variable names cannot contain spaces or most special characters\n */\nfunction escapeCssVarName(name: string): string {\n  // Replace dots with escaped version, handle other special chars\n  return name.replace(/\\./g, \"\\\\.\").replace(/\\s+/g, \"_\");\n}\n\nfunction applyScopedCss(css: string, scopeId: string): string {\n  // Simple scoped CSS implementation\n  // Add the scope attribute selector to each rule\n  const scopeAttr = `[data-v-${scopeId}]`;\n  const slottedScopeAttr = `[data-v-${scopeId}-s]`;\n\n  // Match CSS rule selectors (simplified)\n  // This handles basic cases like .class, #id, element, etc.\n  return css.replace(/([^\\{\\}]+)\\{/g, (match, selectors: string) => {\n    const scopedSelectors = selectors\n      .split(\",\")\n      .map((sel: string) => {\n        sel = sel.trim();\n        if (!sel) return sel;\n\n        // Handle :deep() pseudo-selector\n        if (sel.includes(\":deep(\")) {\n          return sel.replace(/:deep\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Handle :global() pseudo-selector\n        if (sel.includes(\":global(\")) {\n          return sel.replace(/:global\\(([^)]+)\\)/g, \"$1\");\n        }\n\n        // Handle :slotted() and ::v-slotted() pseudo-selector\n        // ::v-slotted(.foo) -> .foo[data-v-xxx-s]\n        // The -s suffix indicates this is a slotted content selector\n        if (sel.includes(\":slotted(\") || sel.includes(\"::v-slotted(\")) {\n          // Extract parts before the slotted selector\n          const slottedMatch = sel.match(/^(.*)(?::slotted|::v-slotted)\\(([^)]+)\\)(.*)$/);\n          if (slottedMatch) {\n            const [, prefix, innerSelector, suffix] = slottedMatch;\n            // Add the slotted scope attribute to the inner selector's last element\n            const innerParts = innerSelector.trim().split(/\\s+/);\n            if (innerParts.length > 0) {\n              innerParts[innerParts.length - 1] += slottedScopeAttr;\n            }\n            // Combine: prefix (if any) + scoped inner selector + suffix (if any)\n            const result = [prefix?.trim(), innerParts.join(\" \"), suffix?.trim()]\n              .filter(Boolean)\n              .join(\" \");\n            return result;\n          }\n        }\n\n        // Add scope to regular selectors\n        // For compound selectors, add scope to the last element\n        const parts = sel.split(/\\s+/);\n        if (parts.length > 0) {\n          const lastPart = parts[parts.length - 1];\n          // Handle pseudo-elements and pseudo-classes\n          if (lastPart.includes(\"::\") || lastPart.match(/:[a-z-]+$/)) {\n            const [base, ...rest] = lastPart.split(/(::?[a-z-]+.*)/);\n            parts[parts.length - 1] = base + scopeAttr + rest.join(\"\");\n          } else {\n            parts[parts.length - 1] = lastPart + scopeAttr;\n          }\n        }\n        return parts.join(\" \");\n      })\n      .join(\", \");\n\n    return scopedSelectors + \" {\";\n  });\n}\n\nexport function generateScopeId(filename: string): string {\n  // Simple hash function for generating scope ID\n  let hash = 0;\n  for (let i = 0; i < filename.length; i++) {\n    const char = filename.charCodeAt(i);\n    hash = (hash << 5) - hash + char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n  return Math.abs(hash).toString(16).slice(0, 8);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\nexport * from \"./compileScript\";\nexport * from \"./compileStyle\";\nexport * from \"./compileSfc\";\nexport * from \"./compileTemplate\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: boolean;\n}\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n  scoped?: boolean;\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        // Check for setup attribute\n        const hasSetup = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"setup\",\n        );\n        scriptBlock.setup = hasSetup;\n        if (hasSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        } else {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        const styleBlock = createBlock(node, source) as SFCStyleBlock;\n        // Check for scoped attribute\n        const hasScoped = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"scoped\",\n        );\n        styleBlock.scoped = hasScoped;\n        descriptor.styles.push(styleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\nimport { parse, compileScript, compileStyle, compileSfc } from \"../packages/compiler-sfc\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"60_basic_sfc_compiler/010_script_setup\", () => {\n  it(\"should parse SFC template block\", () => {\n    const source = `\n<template>\n  <div>Hello SFC!</div>\n</template>\n\n<script>\nexport default {\n  name: 'HelloWorld'\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.template).not.toBeNull();\n    expect(descriptor.template?.content).toContain(\"<div>Hello SFC!</div>\");\n    expect(descriptor.script).not.toBeNull();\n    expect(descriptor.script?.content).toContain(\"name: 'HelloWorld'\");\n  });\n\n  it(\"should compile script setup to setup function\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello!'\nconst count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup(__props, { emit: __emit })\");\n    expect(result.code).toContain(\"const msg = 'Hello!'\");\n    expect(result.code).toContain(\"return { msg, count }\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/020_define_props\", () => {\n  it(\"should compile defineProps with array syntax\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst props = defineProps(['msg', 'count'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['msg', 'count']\");\n    expect(result.code).toContain(\"const props = __props\");\n    expect(result.bindings?.msg).toBe(\"props\");\n    expect(result.bindings?.count).toBe(\"props\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/030_define_emits\", () => {\n  it(\"should compile defineEmits with array syntax\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup>\nconst emit = defineEmits(['click', 'update'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits: ['click', 'update']\");\n    expect(result.code).toContain(\"const emit = __emit\");\n  });\n\n  it(\"should work with defineProps and defineEmits together\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">{{ label }}</button>\n</template>\n\n<script setup>\nconst props = defineProps(['label'])\nconst emit = defineEmits(['click'])\n\nfunction handleClick() {\n  emit('click')\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['label']\");\n    expect(result.code).toContain(\"emits: ['click']\");\n    expect(result.code).toContain(\"const props = __props\");\n    expect(result.code).toContain(\"const emit = __emit\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/040_scoped_css\", () => {\n  it(\"should parse scoped style block\", () => {\n    const source = `\n<template>\n  <div class=\"container\">content</div>\n</template>\n\n<style scoped>\n.container {\n  color: red;\n}\n</style>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.styles.length).toBe(1);\n    expect(descriptor.styles[0].scoped).toBe(true);\n  });\n\n  it(\"should apply scoped transformation to CSS\", () => {\n    const css = `.container { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\"[data-v-abc123]\");\n    expect(result.code).toContain(\".container[data-v-abc123]\");\n  });\n\n  it(\"should not apply scoping to non-scoped styles\", () => {\n    const css = `.container { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: false,\n    });\n\n    expect(result.code).not.toContain(\"[data-v-abc123]\");\n    expect(result.code).toBe(css);\n  });\n\n  it(\"should generate scopeId in compiled SFC\", () => {\n    const source = `\n<template>\n  <div class=\"test\">content</div>\n</template>\n\n<style scoped>\n.test { color: blue; }\n</style>\n`;\n\n    const result = compileSfc(source, \"test.vue\");\n    expect(result.scopeId).toBeDefined();\n    expect(result.code).toContain(\"__scopeId\");\n    expect(result.code).toContain(\"[data-v-\");\n  });\n\n  it(\"should handle multiple style blocks with mixed scoping\", () => {\n    const source = `\n<template>\n  <div class=\"test\">content</div>\n</template>\n\n<style>\n.global { color: black; }\n</style>\n\n<style scoped>\n.scoped { color: blue; }\n</style>\n`;\n\n    const { descriptor } = parse(source);\n    expect(descriptor.styles.length).toBe(2);\n    expect(descriptor.styles[0].scoped).toBe(false);\n    expect(descriptor.styles[1].scoped).toBe(true);\n  });\n\n  it(\"should handle descendant selectors\", () => {\n    const css = `.parent .child { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\".child[data-v-abc123]\");\n  });\n\n  it(\"should handle ::v-slotted() selector\", () => {\n    const css = `::v-slotted(.foo) { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    // ::v-slotted(.foo) should become .foo[data-v-abc123-s]\n    // The -s suffix indicates slotted content\n    expect(result.code).toContain(\".foo[data-v-abc123-s]\");\n    expect(result.code).not.toContain(\"::v-slotted\");\n  });\n\n  it(\"should handle :slotted() selector\", () => {\n    const css = `:slotted(.bar) { color: blue; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"test123\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\".bar[data-v-test123-s]\");\n    expect(result.code).not.toContain(\":slotted\");\n  });\n\n  it(\"should handle v-bind() in CSS values\", () => {\n    const css = `.container { color: v-bind(textColor); }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    // v-bind(textColor) should become var(--abc123-textColor)\n    expect(result.code).toContain(\"var(--abc123-textColor)\");\n    expect(result.code).not.toContain(\"v-bind\");\n    expect(result.cssVars).toContain(\"textColor\");\n  });\n\n  it(\"should handle v-bind() with quoted expressions\", () => {\n    const css = `.container { font-size: v-bind('font.size'); }`;\n    const result = compileStyle({\n      source: css,\n      id: \"test\",\n      scoped: true,\n    });\n\n    // Quoted expression 'font.size' should be extracted\n    expect(result.code).toContain(\"var(--test-font\\\\.size)\");\n    expect(result.cssVars).toContain(\"font.size\");\n  });\n\n  it(\"should handle multiple v-bind() expressions\", () => {\n    const css = `.box { color: v-bind(color); background: v-bind(bg); }`;\n    const result = compileStyle({\n      source: css,\n      id: \"multi\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\"var(--multi-color)\");\n    expect(result.code).toContain(\"var(--multi-bg)\");\n    expect(result.cssVars).toHaveLength(2);\n    expect(result.cssVars).toContain(\"color\");\n    expect(result.cssVars).toContain(\"bg\");\n  });\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/040_scoped_css/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\nimport ChildComponent from './Child.vue'\n\nconst title = ref('Props Destructure')\nconst count = ref(0)\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Props Destructure Example</h2>\n    <ChildComponent :title=\"title\" :count=\"count\" />\n    <button @click=\"count++\">Increment</button>\n    <button @click=\"title = title + '!'\">Update title</button>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/src/Child.vue",
    "content": "<script setup>\n// Props destructure - extract props directly\nconst { title, count = 0 } = defineProps({\n  title: String,\n  count: Number,\n})\n</script>\n\n<template>\n  <div class=\"child\">\n    <h3>Child Component (Props Destructure)</h3>\n    <p>Title: {{ title }}</p>\n    <p>Count: {{ count }}</p>\n  </div>\n</template>\n\n<style>\n.child {\n  padding: 16px;\n  background-color: #f0f0f0;\n  border-radius: 4px;\n  margin: 8px 0;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Handle raw text elements (script, style) - their content should not be parsed as HTML\n  if (isRawTextElement(element.tag)) {\n    const children = parseRawTextContent(context, element.tag);\n    element.children = children;\n\n    // End tag.\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End);\n    }\n\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction isRawTextElement(tag: string): boolean {\n  return tag === \"script\" || tag === \"style\";\n}\n\nfunction parseRawTextContent(context: ParserContext, tag: string): TextNode[] {\n  const start = getCursor(context);\n\n  // Find the closing tag\n  const endTagPattern = new RegExp(`</${tag}[\\\\s>]`, \"i\");\n  const match = context.source.match(endTagPattern);\n\n  if (!match || match.index === undefined) {\n    // No closing tag found, consume all remaining content\n    const content = context.source;\n    advanceBy(context, content.length);\n    return content\n      ? [\n          {\n            type: NodeTypes.TEXT,\n            content,\n            loc: getSelection(context, start),\n          },\n        ]\n      : [];\n  }\n\n  // Extract raw content up to the closing tag\n  const content = context.source.slice(0, match.index);\n  advanceBy(context, content.length);\n\n  if (!content) {\n    return [];\n  }\n\n  return [\n    {\n      type: NodeTypes.TEXT,\n      content,\n      loc: getSelection(context, start),\n    },\n  ];\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/compileScript.ts",
    "content": "import { parse as babelParse } from \"@babel/parser\";\nimport type {\n  Statement,\n  VariableDeclaration,\n  FunctionDeclaration,\n  Identifier,\n  CallExpression,\n  ObjectExpression,\n  ArrayExpression,\n} from \"@babel/types\";\nimport { rewriteDefault } from \"./rewriteDefault\";\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\n\nexport interface SFCScriptCompileResult {\n  code: string;\n  bindings?: Record<string, BindingTypes>;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nconst DEFINE_PROPS = \"defineProps\";\nconst DEFINE_EMITS = \"defineEmits\";\n\nexport function compileScript(sfc: SFCDescriptor): SFCScriptCompileResult {\n  const { script, scriptSetup } = sfc;\n\n  // Handle script setup\n  if (scriptSetup) {\n    return compileScriptSetup(scriptSetup, script);\n  }\n\n  // Handle regular script\n  if (!script) {\n    return {\n      code: \"export default {}\",\n      bindings: {},\n    };\n  }\n\n  let code = script.content;\n\n  // Rewrite default export to __sfc__\n  code = rewriteDefault(code, \"__sfc__\");\n\n  // Add export statement\n  code += \"\\nexport default __sfc__\";\n\n  return {\n    code,\n    bindings: {},\n  };\n}\n\nfunction compileScriptSetup(\n  scriptSetup: SFCScriptBlock,\n  script: SFCScriptBlock | null,\n): SFCScriptCompileResult {\n  const bindings: Record<string, BindingTypes> = {};\n  let code = \"\";\n\n  // Parse the script setup content\n  const ast = babelParse(scriptSetup.content, {\n    sourceType: \"module\",\n    plugins: [\"typescript\"],\n  });\n\n  // Collect top-level bindings (variables, functions)\n  const setupBindings: string[] = [];\n  const imports: string[] = [];\n  const statements: string[] = [];\n  let propsDecl: string | null = null;\n  let propsRuntimeDecl: string | null = null;\n  let emitsDecl: string | null = null;\n  let emitsRuntimeDecl: string | null = null;\n  const propNames: string[] = [];\n  const emitNames: string[] = [];\n\n  for (const node of ast.program.body) {\n    if (node.type === \"ImportDeclaration\") {\n      // Keep import statements (but filter out compiler macros)\n      const filteredSpecifiers = node.specifiers.filter((spec) => {\n        if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n          return ![DEFINE_PROPS, DEFINE_EMITS].includes(spec.imported.name);\n        }\n        return true;\n      });\n      if (filteredSpecifiers.length > 0) {\n        imports.push(scriptSetup.content.slice(node.start!, node.end!));\n      }\n      // Track imported bindings\n      for (const spec of node.specifiers) {\n        const name = spec.local.name;\n        bindings[name] = BindingTypes.SETUP_MAYBE_REF;\n      }\n    } else if (node.type === \"VariableDeclaration\") {\n      const decl = node.declarations[0];\n      if (\n        decl &&\n        decl.init &&\n        decl.init.type === \"CallExpression\" &&\n        decl.init.callee.type === \"Identifier\"\n      ) {\n        const calleeName = decl.init.callee.name;\n\n        // Check for defineProps call\n        if (calleeName === DEFINE_PROPS) {\n          if (decl.id.type === \"Identifier\") {\n            propsDecl = decl.id.name;\n            bindings[propsDecl] = BindingTypes.SETUP_CONST;\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        // Check for defineEmits call\n        if (calleeName === DEFINE_EMITS) {\n          if (decl.id.type === \"Identifier\") {\n            emitsDecl = decl.id.name;\n            bindings[emitsDecl] = BindingTypes.SETUP_CONST;\n            setupBindings.push(emitsDecl);\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n\n      // Track regular variable declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      for (const d of node.declarations) {\n        if (d.id.type === \"Identifier\") {\n          const name = d.id.name;\n          setupBindings.push(name);\n          if (node.kind === \"const\") {\n            bindings[name] = BindingTypes.SETUP_CONST;\n          } else {\n            bindings[name] = BindingTypes.SETUP_LET;\n          }\n        }\n      }\n    } else if (node.type === \"FunctionDeclaration\" && node.id) {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      const name = node.id.name;\n      setupBindings.push(name);\n      bindings[name] = BindingTypes.SETUP_CONST;\n    } else if (node.type === \"ExpressionStatement\") {\n      // Check for standalone defineProps/defineEmits call\n      if (\n        node.expression.type === \"CallExpression\" &&\n        node.expression.callee.type === \"Identifier\"\n      ) {\n        const calleeName = node.expression.callee.name;\n\n        if (calleeName === DEFINE_PROPS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        if (calleeName === DEFINE_EMITS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    } else {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    }\n  }\n\n  // Build the output code\n  code = imports.join(\"\\n\");\n  if (imports.length > 0) {\n    code += \"\\n\\n\";\n  }\n\n  // Handle regular script if present\n  if (script) {\n    code += rewriteDefault(script.content, \"__sfc_main__\");\n    code += \"\\n\\n\";\n  }\n\n  // Create the setup function\n  code += \"const __sfc__ = {\\n\";\n  if (script) {\n    code += \"  ...__sfc_main__,\\n\";\n  }\n\n  // Add props if defined\n  if (propsRuntimeDecl) {\n    code += `  props: ${propsRuntimeDecl},\\n`;\n  }\n\n  // Add emits if defined\n  if (emitsRuntimeDecl) {\n    code += `  emits: ${emitsRuntimeDecl},\\n`;\n  }\n\n  code += \"  setup(__props, { emit: __emit }) {\\n\";\n\n  // Add props destructure if we have a props variable\n  if (propsDecl) {\n    code += `    const ${propsDecl} = __props\\n`;\n  }\n\n  // Add emit function if we have an emit variable\n  if (emitsDecl) {\n    code += `    const ${emitsDecl} = __emit\\n`;\n  }\n\n  for (const stmt of statements) {\n    code += \"    \" + stmt.split(\"\\n\").join(\"\\n    \") + \"\\n\";\n  }\n  code += \"    return { \";\n  code += setupBindings.join(\", \");\n  code += \" }\\n\";\n  code += \"  }\\n\";\n  code += \"}\\n\\n\";\n  code += \"export default __sfc__\";\n\n  return {\n    code,\n    bindings,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/compileSfc.ts",
    "content": "import { compileScript } from \"./compileScript\";\nimport { compileStyle, generateScopeId } from \"./compileStyle\";\nimport { parse } from \"./parse\";\nimport * as CompilerDOM from \"../compiler-dom\";\n\nexport interface SFCCompileResult {\n  code: string;\n  scopeId?: string;\n}\n\nexport function compileSfc(source: string, filename: string = \"anonymous.vue\"): SFCCompileResult {\n  // Parse the SFC\n  const { descriptor } = parse(source, { filename });\n\n  // Generate scope ID if we have scoped styles\n  const hasScoped = descriptor.styles.some((s) => s.scoped);\n  const scopeId = hasScoped ? generateScopeId(filename) : undefined;\n\n  // Compile script\n  const scriptResult = compileScript(descriptor);\n\n  // Compile template\n  let templateCode = \"\";\n  if (descriptor.template) {\n    const templateContent = descriptor.template.content;\n    const compiledTemplate = CompilerDOM.compile(templateContent);\n    templateCode = compiledTemplate;\n  }\n\n  // Generate final code\n  let code = \"\";\n\n  // Add script code\n  code += scriptResult.code;\n\n  // Add __scopeId if scoped\n  if (scopeId) {\n    code += `\\n__sfc__.__scopeId = \"data-v-${scopeId}\"`;\n  }\n\n  // Add template as render function\n  if (templateCode) {\n    code += `\\n\\n${templateCode}`;\n    code += `\\n__sfc__.render = render`;\n  }\n\n  // Add styles (with scoped transformation if needed)\n  if (descriptor.styles.length > 0) {\n    const compiledStyles = descriptor.styles.map((styleBlock) => {\n      const result = compileStyle({\n        source: styleBlock.content,\n        id: scopeId || \"\",\n        scoped: styleBlock.scoped,\n      });\n      return result.code;\n    });\n\n    const styles = compiledStyles.join(\"\\n\");\n    code += `\\n\\n// Styles\\nconst __style__ = \\`${styles.replace(/`/g, \"\\\\`\")}\\`;`;\n    code += `\\nif (typeof document !== 'undefined') {`;\n    code += `\\n  const __styleEl__ = document.createElement('style');`;\n    code += `\\n  __styleEl__.textContent = __style__;`;\n    code += `\\n  document.head.appendChild(__styleEl__);`;\n    code += `\\n}`;\n  }\n\n  return { code, scopeId };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/compileStyle.ts",
    "content": "import type { SFCStyleBlock } from \"./parse\";\n\nexport interface SFCStyleCompileOptions {\n  source: string;\n  id: string;\n  scoped?: boolean;\n}\n\nexport interface SFCStyleCompileResult {\n  code: string;\n  /** CSS variable names extracted from v-bind() expressions */\n  cssVars?: string[];\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): SFCStyleCompileResult {\n  const { source, id, scoped } = options;\n\n  // First, process v-bind() expressions\n  const { code: processedCss, cssVars } = processVBind(source, id);\n\n  if (!scoped) {\n    return { code: processedCss, cssVars };\n  }\n\n  // Apply scoped transformation\n  const scopedCode = applyScopedCss(processedCss, id);\n\n  return { code: scopedCode, cssVars };\n}\n\n/**\n * Process v-bind() expressions in CSS\n *\n * Converts v-bind(expr) to var(--{id}-{expr})\n *\n * NOTE: v-bind() in CSS has performance implications:\n * - Each v-bind() creates a CSS custom property that is set via inline style\n * - The values are updated reactively when the expression changes\n * - This triggers style recalculation on every update\n * - For frequently changing values, consider using inline styles instead\n */\nfunction processVBind(css: string, id: string): { code: string; cssVars: string[] } {\n  const cssVars: string[] = [];\n  const vBindRE = /v-bind\\s*\\(\\s*([^)]+)\\s*\\)/g;\n\n  const code = css.replace(vBindRE, (match, expr: string) => {\n    // Normalize the expression (remove quotes if present)\n    let varName = expr.trim();\n    if (\n      (varName.startsWith(\"'\") && varName.endsWith(\"'\")) ||\n      (varName.startsWith('\"') && varName.endsWith('\"'))\n    ) {\n      varName = varName.slice(1, -1);\n    }\n\n    // Escape special characters for CSS variable names\n    const escapedVarName = escapeCssVarName(varName);\n\n    if (!cssVars.includes(varName)) {\n      cssVars.push(varName);\n    }\n\n    return `var(--${id}-${escapedVarName})`;\n  });\n\n  return { code, cssVars };\n}\n\n/**\n * Escape special characters in CSS variable names\n * CSS variable names cannot contain spaces or most special characters\n */\nfunction escapeCssVarName(name: string): string {\n  // Replace dots with escaped version, handle other special chars\n  return name.replace(/\\./g, \"\\\\.\").replace(/\\s+/g, \"_\");\n}\n\nfunction applyScopedCss(css: string, scopeId: string): string {\n  // Simple scoped CSS implementation\n  // Add the scope attribute selector to each rule\n  const scopeAttr = `[data-v-${scopeId}]`;\n  const slottedScopeAttr = `[data-v-${scopeId}-s]`;\n\n  // Match CSS rule selectors (simplified)\n  // This handles basic cases like .class, #id, element, etc.\n  return css.replace(/([^\\{\\}]+)\\{/g, (match, selectors: string) => {\n    const scopedSelectors = selectors\n      .split(\",\")\n      .map((sel: string) => {\n        sel = sel.trim();\n        if (!sel) return sel;\n\n        // Handle :deep() pseudo-selector\n        if (sel.includes(\":deep(\")) {\n          return sel.replace(/:deep\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Handle :global() pseudo-selector\n        if (sel.includes(\":global(\")) {\n          return sel.replace(/:global\\(([^)]+)\\)/g, \"$1\");\n        }\n\n        // Handle :slotted() and ::v-slotted() pseudo-selector\n        // ::v-slotted(.foo) -> .foo[data-v-xxx-s]\n        // The -s suffix indicates this is a slotted content selector\n        if (sel.includes(\":slotted(\") || sel.includes(\"::v-slotted(\")) {\n          // Extract parts before the slotted selector\n          const slottedMatch = sel.match(/^(.*)(?::slotted|::v-slotted)\\(([^)]+)\\)(.*)$/);\n          if (slottedMatch) {\n            const [, prefix, innerSelector, suffix] = slottedMatch;\n            // Add the slotted scope attribute to the inner selector's last element\n            const innerParts = innerSelector.trim().split(/\\s+/);\n            if (innerParts.length > 0) {\n              innerParts[innerParts.length - 1] += slottedScopeAttr;\n            }\n            // Combine: prefix (if any) + scoped inner selector + suffix (if any)\n            const result = [prefix?.trim(), innerParts.join(\" \"), suffix?.trim()]\n              .filter(Boolean)\n              .join(\" \");\n            return result;\n          }\n        }\n\n        // Add scope to regular selectors\n        // For compound selectors, add scope to the last element\n        const parts = sel.split(/\\s+/);\n        if (parts.length > 0) {\n          const lastPart = parts[parts.length - 1];\n          // Handle pseudo-elements and pseudo-classes\n          if (lastPart.includes(\"::\") || lastPart.match(/:[a-z-]+$/)) {\n            const [base, ...rest] = lastPart.split(/(::?[a-z-]+.*)/);\n            parts[parts.length - 1] = base + scopeAttr + rest.join(\"\");\n          } else {\n            parts[parts.length - 1] = lastPart + scopeAttr;\n          }\n        }\n        return parts.join(\" \");\n      })\n      .join(\", \");\n\n    return scopedSelectors + \" {\";\n  });\n}\n\nexport function generateScopeId(filename: string): string {\n  // Simple hash function for generating scope ID\n  let hash = 0;\n  for (let i = 0; i < filename.length; i++) {\n    const char = filename.charCodeAt(i);\n    hash = (hash << 5) - hash + char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n  return Math.abs(hash).toString(16).slice(0, 8);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\nexport * from \"./compileScript\";\nexport * from \"./compileStyle\";\nexport * from \"./compileSfc\";\nexport * from \"./compileTemplate\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: boolean;\n}\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n  scoped?: boolean;\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        // Check for setup attribute\n        const hasSetup = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"setup\",\n        );\n        scriptBlock.setup = hasSetup;\n        if (hasSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        } else {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        const styleBlock = createBlock(node, source) as SFCStyleBlock;\n        // Check for scoped attribute\n        const hasScoped = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"scoped\",\n        );\n        styleBlock.scoped = hasScoped;\n        descriptor.styles.push(styleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\nimport { parse, compileScript, compileStyle, compileSfc } from \"../packages/compiler-sfc\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"60_basic_sfc_compiler/010_script_setup\", () => {\n  it(\"should compile script setup to setup function\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello!'\nconst count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup(__props, { emit: __emit })\");\n    expect(result.code).toContain(\"const msg = 'Hello!'\");\n    expect(result.code).toContain(\"return { msg, count }\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/020_define_props\", () => {\n  it(\"should compile defineProps with array syntax\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst props = defineProps(['msg', 'count'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props: ['msg', 'count']\");\n    expect(result.code).toContain(\"const props = __props\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/030_define_emits\", () => {\n  it(\"should compile defineEmits with array syntax\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup>\nconst emit = defineEmits(['click', 'update'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits: ['click', 'update']\");\n    expect(result.code).toContain(\"const emit = __emit\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/040_scoped_css\", () => {\n  it(\"should apply scoped transformation to CSS\", () => {\n    const css = `.container { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\"[data-v-abc123]\");\n    expect(result.code).toContain(\".container[data-v-abc123]\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/050_props_destructure\", () => {\n  // Props destructure is a Vue 3.5 feature\n  // This is a placeholder for future implementation\n  // See the book chapter for implementation details\n\n  it.skip(\"should handle props destructure with default values\", () => {\n    const source = `\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    // When implemented, destructured props should be transformed to __props.xxx\n    expect(result.code).toContain(\"__props.count\");\n    expect(result.code).toContain(\"__props.message\");\n  });\n\n  it.skip(\"should register destructured props as PROPS bindings\", () => {\n    const source = `\n<template>\n  <p>{{ count }}</p>\n</template>\n\n<script setup>\nconst { count } = defineProps(['count'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.bindings?.count).toBe(\"props\");\n  });\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/050_props_destructure/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'chibivue'\nimport ChildComponent from './Child.vue'\n\nconst message = ref('Hello, Type-based Macros!')\nconst count = ref(0)\nconst logs = ref<string[]>([])\n\nconst handleUpdate = (payload: { message: string; count: number }) => {\n  logs.value.push(`Received: message=${payload.message}, count=${payload.count}`)\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Type-based Macros Example</h2>\n    <ChildComponent :message=\"message\" :count=\"count\" @update=\"handleUpdate\" />\n    <button @click=\"count++\">Increment count</button>\n    <h3>Event Logs:</h3>\n    <ul>\n      <li v-for=\"(log, i) in logs\" :key=\"i\">{{ log }}</li>\n    </ul>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/src/Child.vue",
    "content": "<script setup lang=\"ts\">\n// Type-based props definition\nconst props = defineProps<{\n  message: string\n  count: number\n}>()\n\n// Type-based emits definition\nconst emit = defineEmits<{\n  (e: 'update', payload: { message: string; count: number }): void\n}>()\n\nconst sendUpdate = () => {\n  emit('update', { message: props.message, count: props.count })\n}\n</script>\n\n<template>\n  <div class=\"child\">\n    <h3>Child Component (Type-based Macros)</h3>\n    <p>Message: {{ props.message }}</p>\n    <p>Count: {{ props.count }}</p>\n    <button @click=\"sendUpdate\">Emit update</button>\n  </div>\n</template>\n\n<style>\n.child {\n  padding: 16px;\n  background-color: #f0f0f0;\n  border-radius: 4px;\n  margin: 8px 0;\n}\n</style>\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Handle raw text elements (script, style) - their content should not be parsed as HTML\n  if (isRawTextElement(element.tag)) {\n    const children = parseRawTextContent(context, element.tag);\n    element.children = children;\n\n    // End tag.\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End);\n    }\n\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction isRawTextElement(tag: string): boolean {\n  return tag === \"script\" || tag === \"style\";\n}\n\nfunction parseRawTextContent(context: ParserContext, tag: string): TextNode[] {\n  const start = getCursor(context);\n\n  // Find the closing tag\n  const endTagPattern = new RegExp(`</${tag}[\\\\s>]`, \"i\");\n  const match = context.source.match(endTagPattern);\n\n  if (!match || match.index === undefined) {\n    // No closing tag found, consume all remaining content\n    const content = context.source;\n    advanceBy(context, content.length);\n    return content\n      ? [\n          {\n            type: NodeTypes.TEXT,\n            content,\n            loc: getSelection(context, start),\n          },\n        ]\n      : [];\n  }\n\n  // Extract raw content up to the closing tag\n  const content = context.source.slice(0, match.index);\n  advanceBy(context, content.length);\n\n  if (!content) {\n    return [];\n  }\n\n  return [\n    {\n      type: NodeTypes.TEXT,\n      content,\n      loc: getSelection(context, start),\n    },\n  ];\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/compileScript.ts",
    "content": "import { parse as babelParse } from \"@babel/parser\";\nimport type {\n  Statement,\n  VariableDeclaration,\n  FunctionDeclaration,\n  Identifier,\n  CallExpression,\n  ObjectExpression,\n  ArrayExpression,\n  TSTypeLiteral,\n  TSCallSignatureDeclaration,\n  TSPropertySignature,\n} from \"@babel/types\";\nimport { rewriteDefault } from \"./rewriteDefault\";\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\n\nexport interface SFCScriptCompileResult {\n  code: string;\n  bindings?: Record<string, BindingTypes>;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nconst DEFINE_PROPS = \"defineProps\";\nconst DEFINE_EMITS = \"defineEmits\";\n\n/**\n * Extract emit names from type literal\n * Supports two formats:\n * 1. Function overload: { (e: 'event', ...): void }\n * 2. Object format (Vue 3.3+): { event: [arg] }\n */\nfunction extractEmitsFromTypeLiteral(typeLiteral: TSTypeLiteral): string[] {\n  const emitNames: string[] = [];\n\n  for (const member of typeLiteral.members) {\n    // Function overload format: { (e: 'event', value: string): void }\n    if (member.type === \"TSCallSignatureDeclaration\") {\n      const callSig = member as TSCallSignatureDeclaration;\n      if (callSig.parameters && callSig.parameters.length > 0) {\n        const firstParam = callSig.parameters[0];\n        if (firstParam.typeAnnotation && firstParam.typeAnnotation.typeAnnotation) {\n          const typeAnn = firstParam.typeAnnotation.typeAnnotation;\n          // Check for literal type: 'event'\n          if (typeAnn.type === \"TSLiteralType\" && typeAnn.literal.type === \"StringLiteral\") {\n            emitNames.push(typeAnn.literal.value);\n          }\n        }\n      }\n    }\n    // Object format: { event: [arg] }\n    else if (member.type === \"TSPropertySignature\") {\n      const propSig = member as TSPropertySignature;\n      if (propSig.key.type === \"Identifier\") {\n        emitNames.push(propSig.key.name);\n      } else if (propSig.key.type === \"StringLiteral\") {\n        emitNames.push(propSig.key.value);\n      }\n    }\n  }\n\n  return emitNames;\n}\n\nexport function compileScript(sfc: SFCDescriptor): SFCScriptCompileResult {\n  const { script, scriptSetup } = sfc;\n\n  // Handle script setup\n  if (scriptSetup) {\n    return compileScriptSetup(scriptSetup, script);\n  }\n\n  // Handle regular script\n  if (!script) {\n    return {\n      code: \"export default {}\",\n      bindings: {},\n    };\n  }\n\n  let code = script.content;\n\n  // Rewrite default export to __sfc__\n  code = rewriteDefault(code, \"__sfc__\");\n\n  // Add export statement\n  code += \"\\nexport default __sfc__\";\n\n  return {\n    code,\n    bindings: {},\n  };\n}\n\nfunction compileScriptSetup(\n  scriptSetup: SFCScriptBlock,\n  script: SFCScriptBlock | null,\n): SFCScriptCompileResult {\n  const bindings: Record<string, BindingTypes> = {};\n  let code = \"\";\n\n  // Parse the script setup content\n  const ast = babelParse(scriptSetup.content, {\n    sourceType: \"module\",\n    plugins: [\"typescript\"],\n  });\n\n  // Collect top-level bindings (variables, functions)\n  const setupBindings: string[] = [];\n  const imports: string[] = [];\n  const statements: string[] = [];\n  let propsDecl: string | null = null;\n  let propsRuntimeDecl: string | null = null;\n  let emitsDecl: string | null = null;\n  let emitsRuntimeDecl: string | null = null;\n  const propNames: string[] = [];\n  const emitNames: string[] = [];\n\n  for (const node of ast.program.body) {\n    if (node.type === \"ImportDeclaration\") {\n      // Keep import statements (but filter out compiler macros)\n      const filteredSpecifiers = node.specifiers.filter((spec) => {\n        if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n          return ![DEFINE_PROPS, DEFINE_EMITS].includes(spec.imported.name);\n        }\n        return true;\n      });\n      if (filteredSpecifiers.length > 0) {\n        imports.push(scriptSetup.content.slice(node.start!, node.end!));\n      }\n      // Track imported bindings\n      for (const spec of node.specifiers) {\n        const name = spec.local.name;\n        bindings[name] = BindingTypes.SETUP_MAYBE_REF;\n      }\n    } else if (node.type === \"VariableDeclaration\") {\n      const decl = node.declarations[0];\n      if (\n        decl &&\n        decl.init &&\n        decl.init.type === \"CallExpression\" &&\n        decl.init.callee.type === \"Identifier\"\n      ) {\n        const calleeName = decl.init.callee.name;\n\n        // Check for defineProps call\n        if (calleeName === DEFINE_PROPS) {\n          if (decl.id.type === \"Identifier\") {\n            propsDecl = decl.id.name;\n            bindings[propsDecl] = BindingTypes.SETUP_CONST;\n          }\n          const arg = decl.init.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        // Check for defineEmits call\n        if (calleeName === DEFINE_EMITS) {\n          if (decl.id.type === \"Identifier\") {\n            emitsDecl = decl.id.name;\n            bindings[emitsDecl] = BindingTypes.SETUP_CONST;\n            setupBindings.push(emitsDecl);\n          }\n\n          // Check for type-based declaration: defineEmits<...>()\n          const typeParams = decl.init.typeParameters;\n          if (typeParams && typeParams.params.length > 0) {\n            const typeArg = typeParams.params[0];\n            if (typeArg.type === \"TSTypeLiteral\") {\n              const extractedEmits = extractEmitsFromTypeLiteral(typeArg);\n              emitNames.push(...extractedEmits);\n              emitsRuntimeDecl = `[${extractedEmits.map((e) => `'${e}'`).join(\", \")}]`;\n            }\n          } else {\n            // Runtime declaration\n            const arg = decl.init.arguments[0];\n            if (arg) {\n              if (arg.type === \"ArrayExpression\") {\n                for (const el of arg.elements) {\n                  if (el && el.type === \"StringLiteral\") {\n                    emitNames.push(el.value);\n                  }\n                }\n                emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n              } else if (arg.type === \"ObjectExpression\") {\n                for (const prop of arg.properties) {\n                  if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                    emitNames.push(prop.key.name);\n                  }\n                }\n                emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n              }\n            }\n          }\n          continue;\n        }\n      }\n\n      // Track regular variable declarations\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      for (const d of node.declarations) {\n        if (d.id.type === \"Identifier\") {\n          const name = d.id.name;\n          setupBindings.push(name);\n          if (node.kind === \"const\") {\n            bindings[name] = BindingTypes.SETUP_CONST;\n          } else {\n            bindings[name] = BindingTypes.SETUP_LET;\n          }\n        }\n      }\n    } else if (node.type === \"FunctionDeclaration\" && node.id) {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n      const name = node.id.name;\n      setupBindings.push(name);\n      bindings[name] = BindingTypes.SETUP_CONST;\n    } else if (node.type === \"ExpressionStatement\") {\n      // Check for standalone defineProps/defineEmits call\n      if (\n        node.expression.type === \"CallExpression\" &&\n        node.expression.callee.type === \"Identifier\"\n      ) {\n        const calleeName = node.expression.callee.name;\n\n        if (calleeName === DEFINE_PROPS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  propNames.push(el.value);\n                  bindings[el.value] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  propNames.push(prop.key.name);\n                  bindings[prop.key.name] = BindingTypes.PROPS;\n                }\n              }\n              propsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n\n        if (calleeName === DEFINE_EMITS) {\n          const arg = node.expression.arguments[0];\n          if (arg) {\n            if (arg.type === \"ArrayExpression\") {\n              for (const el of arg.elements) {\n                if (el && el.type === \"StringLiteral\") {\n                  emitNames.push(el.value);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            } else if (arg.type === \"ObjectExpression\") {\n              for (const prop of arg.properties) {\n                if (prop.type === \"ObjectProperty\" && prop.key.type === \"Identifier\") {\n                  emitNames.push(prop.key.name);\n                }\n              }\n              emitsRuntimeDecl = scriptSetup.content.slice(arg.start!, arg.end!);\n            }\n          }\n          continue;\n        }\n      }\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    } else {\n      statements.push(scriptSetup.content.slice(node.start!, node.end!));\n    }\n  }\n\n  // Build the output code\n  code = imports.join(\"\\n\");\n  if (imports.length > 0) {\n    code += \"\\n\\n\";\n  }\n\n  // Handle regular script if present\n  if (script) {\n    code += rewriteDefault(script.content, \"__sfc_main__\");\n    code += \"\\n\\n\";\n  }\n\n  // Create the setup function\n  code += \"const __sfc__ = {\\n\";\n  if (script) {\n    code += \"  ...__sfc_main__,\\n\";\n  }\n\n  // Add props if defined\n  if (propsRuntimeDecl) {\n    code += `  props: ${propsRuntimeDecl},\\n`;\n  }\n\n  // Add emits if defined\n  if (emitsRuntimeDecl) {\n    code += `  emits: ${emitsRuntimeDecl},\\n`;\n  }\n\n  code += \"  setup(__props, { emit: __emit }) {\\n\";\n\n  // Add props destructure if we have a props variable\n  if (propsDecl) {\n    code += `    const ${propsDecl} = __props\\n`;\n  }\n\n  // Add emit function if we have an emit variable\n  if (emitsDecl) {\n    code += `    const ${emitsDecl} = __emit\\n`;\n  }\n\n  for (const stmt of statements) {\n    code += \"    \" + stmt.split(\"\\n\").join(\"\\n    \") + \"\\n\";\n  }\n  code += \"    return { \";\n  code += setupBindings.join(\", \");\n  code += \" }\\n\";\n  code += \"  }\\n\";\n  code += \"}\\n\\n\";\n  code += \"export default __sfc__\";\n\n  return {\n    code,\n    bindings,\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/compileSfc.ts",
    "content": "import { compileScript } from \"./compileScript\";\nimport { compileStyle, generateScopeId } from \"./compileStyle\";\nimport { parse } from \"./parse\";\nimport * as CompilerDOM from \"../compiler-dom\";\n\nexport interface SFCCompileResult {\n  code: string;\n  scopeId?: string;\n}\n\nexport function compileSfc(source: string, filename: string = \"anonymous.vue\"): SFCCompileResult {\n  // Parse the SFC\n  const { descriptor } = parse(source, { filename });\n\n  // Generate scope ID if we have scoped styles\n  const hasScoped = descriptor.styles.some((s) => s.scoped);\n  const scopeId = hasScoped ? generateScopeId(filename) : undefined;\n\n  // Compile script\n  const scriptResult = compileScript(descriptor);\n\n  // Compile template\n  let templateCode = \"\";\n  if (descriptor.template) {\n    const templateContent = descriptor.template.content;\n    const compiledTemplate = CompilerDOM.compile(templateContent);\n    templateCode = compiledTemplate;\n  }\n\n  // Generate final code\n  let code = \"\";\n\n  // Add script code\n  code += scriptResult.code;\n\n  // Add __scopeId if scoped\n  if (scopeId) {\n    code += `\\n__sfc__.__scopeId = \"data-v-${scopeId}\"`;\n  }\n\n  // Add template as render function\n  if (templateCode) {\n    code += `\\n\\n${templateCode}`;\n    code += `\\n__sfc__.render = render`;\n  }\n\n  // Add styles (with scoped transformation if needed)\n  if (descriptor.styles.length > 0) {\n    const compiledStyles = descriptor.styles.map((styleBlock) => {\n      const result = compileStyle({\n        source: styleBlock.content,\n        id: scopeId || \"\",\n        scoped: styleBlock.scoped,\n      });\n      return result.code;\n    });\n\n    const styles = compiledStyles.join(\"\\n\");\n    code += `\\n\\n// Styles\\nconst __style__ = \\`${styles.replace(/`/g, \"\\\\`\")}\\`;`;\n    code += `\\nif (typeof document !== 'undefined') {`;\n    code += `\\n  const __styleEl__ = document.createElement('style');`;\n    code += `\\n  __styleEl__.textContent = __style__;`;\n    code += `\\n  document.head.appendChild(__styleEl__);`;\n    code += `\\n}`;\n  }\n\n  return { code, scopeId };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/compileStyle.ts",
    "content": "import type { SFCStyleBlock } from \"./parse\";\n\nexport interface SFCStyleCompileOptions {\n  source: string;\n  id: string;\n  scoped?: boolean;\n}\n\nexport interface SFCStyleCompileResult {\n  code: string;\n  /** CSS variable names extracted from v-bind() expressions */\n  cssVars?: string[];\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): SFCStyleCompileResult {\n  const { source, id, scoped } = options;\n\n  // First, process v-bind() expressions\n  const { code: processedCss, cssVars } = processVBind(source, id);\n\n  if (!scoped) {\n    return { code: processedCss, cssVars };\n  }\n\n  // Apply scoped transformation\n  const scopedCode = applyScopedCss(processedCss, id);\n\n  return { code: scopedCode, cssVars };\n}\n\n/**\n * Process v-bind() expressions in CSS\n *\n * Converts v-bind(expr) to var(--{id}-{expr})\n *\n * NOTE: v-bind() in CSS has performance implications:\n * - Each v-bind() creates a CSS custom property that is set via inline style\n * - The values are updated reactively when the expression changes\n * - This triggers style recalculation on every update\n * - For frequently changing values, consider using inline styles instead\n */\nfunction processVBind(css: string, id: string): { code: string; cssVars: string[] } {\n  const cssVars: string[] = [];\n  const vBindRE = /v-bind\\s*\\(\\s*([^)]+)\\s*\\)/g;\n\n  const code = css.replace(vBindRE, (match, expr: string) => {\n    // Normalize the expression (remove quotes if present)\n    let varName = expr.trim();\n    if (\n      (varName.startsWith(\"'\") && varName.endsWith(\"'\")) ||\n      (varName.startsWith('\"') && varName.endsWith('\"'))\n    ) {\n      varName = varName.slice(1, -1);\n    }\n\n    // Escape special characters for CSS variable names\n    const escapedVarName = escapeCssVarName(varName);\n\n    if (!cssVars.includes(varName)) {\n      cssVars.push(varName);\n    }\n\n    return `var(--${id}-${escapedVarName})`;\n  });\n\n  return { code, cssVars };\n}\n\n/**\n * Escape special characters in CSS variable names\n * CSS variable names cannot contain spaces or most special characters\n */\nfunction escapeCssVarName(name: string): string {\n  // Replace dots with escaped version, handle other special chars\n  return name.replace(/\\./g, \"\\\\.\").replace(/\\s+/g, \"_\");\n}\n\nfunction applyScopedCss(css: string, scopeId: string): string {\n  // Simple scoped CSS implementation\n  // Add the scope attribute selector to each rule\n  const scopeAttr = `[data-v-${scopeId}]`;\n  const slottedScopeAttr = `[data-v-${scopeId}-s]`;\n\n  // Match CSS rule selectors (simplified)\n  // This handles basic cases like .class, #id, element, etc.\n  return css.replace(/([^\\{\\}]+)\\{/g, (match, selectors: string) => {\n    const scopedSelectors = selectors\n      .split(\",\")\n      .map((sel: string) => {\n        sel = sel.trim();\n        if (!sel) return sel;\n\n        // Handle :deep() pseudo-selector\n        if (sel.includes(\":deep(\")) {\n          return sel.replace(/:deep\\(([^)]+)\\)/g, `${scopeAttr} $1`);\n        }\n\n        // Handle :global() pseudo-selector\n        if (sel.includes(\":global(\")) {\n          return sel.replace(/:global\\(([^)]+)\\)/g, \"$1\");\n        }\n\n        // Handle :slotted() and ::v-slotted() pseudo-selector\n        // ::v-slotted(.foo) -> .foo[data-v-xxx-s]\n        // The -s suffix indicates this is a slotted content selector\n        if (sel.includes(\":slotted(\") || sel.includes(\"::v-slotted(\")) {\n          // Extract parts before the slotted selector\n          const slottedMatch = sel.match(/^(.*)(?::slotted|::v-slotted)\\(([^)]+)\\)(.*)$/);\n          if (slottedMatch) {\n            const [, prefix, innerSelector, suffix] = slottedMatch;\n            // Add the slotted scope attribute to the inner selector's last element\n            const innerParts = innerSelector.trim().split(/\\s+/);\n            if (innerParts.length > 0) {\n              innerParts[innerParts.length - 1] += slottedScopeAttr;\n            }\n            // Combine: prefix (if any) + scoped inner selector + suffix (if any)\n            const result = [prefix?.trim(), innerParts.join(\" \"), suffix?.trim()]\n              .filter(Boolean)\n              .join(\" \");\n            return result;\n          }\n        }\n\n        // Add scope to regular selectors\n        // For compound selectors, add scope to the last element\n        const parts = sel.split(/\\s+/);\n        if (parts.length > 0) {\n          const lastPart = parts[parts.length - 1];\n          // Handle pseudo-elements and pseudo-classes\n          if (lastPart.includes(\"::\") || lastPart.match(/:[a-z-]+$/)) {\n            const [base, ...rest] = lastPart.split(/(::?[a-z-]+.*)/);\n            parts[parts.length - 1] = base + scopeAttr + rest.join(\"\");\n          } else {\n            parts[parts.length - 1] = lastPart + scopeAttr;\n          }\n        }\n        return parts.join(\" \");\n      })\n      .join(\", \");\n\n    return scopedSelectors + \" {\";\n  });\n}\n\nexport function generateScopeId(filename: string): string {\n  // Simple hash function for generating scope ID\n  let hash = 0;\n  for (let i = 0; i < filename.length; i++) {\n    const char = filename.charCodeAt(i);\n    hash = (hash << 5) - hash + char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n  return Math.abs(hash).toString(16).slice(0, 8);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\nexport * from \"./compileScript\";\nexport * from \"./compileStyle\";\nexport * from \"./compileSfc\";\nexport * from \"./compileTemplate\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: boolean;\n}\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n  scoped?: boolean;\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        // Check for setup attribute\n        const hasSetup = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"setup\",\n        );\n        scriptBlock.setup = hasSetup;\n        if (hasSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        } else {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        const styleBlock = createBlock(node, source) as SFCStyleBlock;\n        // Check for scoped attribute\n        const hasScoped = node.props.some(\n          (p) => p.type === NodeTypes.ATTRIBUTE && p.name === \"scoped\",\n        );\n        styleBlock.scoped = hasScoped;\n        descriptor.styles.push(styleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, {}, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance) => {\n  currentInstance = instance;\n  instance.scope.on();\n};\n\nexport const unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance();\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport { registerRuntimeCompiler, type InternalRenderFunction } from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport { createVNode, createCommentVNode, Fragment } from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, h } from \"../packages\";\nimport { parse, compileScript, compileStyle, compileSfc } from \"../packages/compiler-sfc\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"60_basic_sfc_compiler/010_script_setup\", () => {\n  it(\"should compile script setup to setup function\", () => {\n    const source = `\n<template>\n  <div>{{ msg }}</div>\n</template>\n\n<script setup>\nconst msg = 'Hello!'\nconst count = 0\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"setup(__props, { emit: __emit })\");\n    expect(result.code).toContain(\"const msg = 'Hello!'\");\n    expect(result.code).toContain(\"return { msg, count }\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/020_define_props\", () => {\n  it(\"should compile defineProps with object syntax\", () => {\n    const source = `\n<template>\n  <div>{{ title }}</div>\n</template>\n\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"props:\");\n    expect(result.code).toContain(\"title: String\");\n    expect(result.code).toContain(\"count: Number\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/030_define_emits\", () => {\n  it(\"should compile defineEmits with array syntax\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup>\nconst emit = defineEmits(['click', 'update'])\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits: ['click', 'update']\");\n    expect(result.code).toContain(\"const emit = __emit\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/040_scoped_css\", () => {\n  it(\"should apply scoped transformation to CSS\", () => {\n    const css = `.container { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\"[data-v-abc123]\");\n    expect(result.code).toContain(\".container[data-v-abc123]\");\n  });\n\n  it(\"should handle ::v-slotted() selector\", () => {\n    const css = `::v-slotted(.foo) { color: red; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    // ::v-slotted(.foo) should become .foo[data-v-abc123-s]\n    // The -s suffix indicates slotted content\n    expect(result.code).toContain(\".foo[data-v-abc123-s]\");\n    expect(result.code).not.toContain(\"::v-slotted\");\n  });\n\n  it(\"should handle :slotted() selector\", () => {\n    const css = `:slotted(.bar) { color: blue; }`;\n    const result = compileStyle({\n      source: css,\n      id: \"test123\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\".bar[data-v-test123-s]\");\n    expect(result.code).not.toContain(\":slotted\");\n  });\n\n  it(\"should handle v-bind() in CSS values\", () => {\n    const css = `.container { color: v-bind(textColor); }`;\n    const result = compileStyle({\n      source: css,\n      id: \"abc123\",\n      scoped: true,\n    });\n\n    // v-bind(textColor) should become var(--abc123-textColor)\n    expect(result.code).toContain(\"var(--abc123-textColor)\");\n    expect(result.code).not.toContain(\"v-bind\");\n    expect(result.cssVars).toContain(\"textColor\");\n  });\n\n  it(\"should handle v-bind() with quoted expressions\", () => {\n    const css = `.container { font-size: v-bind('font.size'); }`;\n    const result = compileStyle({\n      source: css,\n      id: \"test\",\n      scoped: true,\n    });\n\n    // Quoted expression 'font.size' should be extracted\n    expect(result.code).toContain(\"var(--test-font\\\\.size)\");\n    expect(result.cssVars).toContain(\"font.size\");\n  });\n\n  it(\"should handle multiple v-bind() expressions\", () => {\n    const css = `.box { color: v-bind(color); background: v-bind(bg); }`;\n    const result = compileStyle({\n      source: css,\n      id: \"multi\",\n      scoped: true,\n    });\n\n    expect(result.code).toContain(\"var(--multi-color)\");\n    expect(result.code).toContain(\"var(--multi-bg)\");\n    expect(result.cssVars).toHaveLength(2);\n    expect(result.cssVars).toContain(\"color\");\n    expect(result.cssVars).toContain(\"bg\");\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/050_props_destructure\", () => {\n  it.skip(\"should handle props destructure\", () => {\n    // Placeholder for props destructure implementation\n  });\n});\n\ndescribe(\"60_basic_sfc_compiler/060_type_based_macros\", () => {\n  // Type-based defineEmits\n\n  it(\"should compile type-based defineEmits with function overload format\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup lang=\"ts\">\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n\nfunction handleClick() {\n  emit('change', 'test')\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits:\");\n    expect(result.code).toContain(\"'change'\");\n    expect(result.code).toContain(\"'update'\");\n  });\n\n  it(\"should compile type-based defineEmits with object format\", () => {\n    const source = `\n<template>\n  <button @click=\"handleClick\">Click</button>\n</template>\n\n<script setup lang=\"ts\">\nconst emit = defineEmits<{\n  change: [value: string]\n  update: [id: number]\n}>()\n\nfunction handleClick() {\n  emit('change', 'test')\n}\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    expect(result.code).toContain(\"emits:\");\n    expect(result.code).toContain(\"'change'\");\n    expect(result.code).toContain(\"'update'\");\n  });\n\n  it.skip(\"should compile type-based defineProps\", () => {\n    const source = `\n<template>\n  <div>{{ count }}</div>\n</template>\n\n<script setup lang=\"ts\">\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    // When implemented, TypeScript types should be converted to runtime props\n    expect(result.code).toContain(\"props:\");\n    expect(result.code).toContain(\"type: Number\");\n    expect(result.code).toContain(\"required: true\");\n  });\n\n  it.skip(\"should handle withDefaults for type-based props\", () => {\n    const source = `\n<template>\n  <div>{{ message }}</div>\n</template>\n\n<script setup lang=\"ts\">\ninterface Props {\n  count: number\n  message?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  message: 'default message'\n})\n</script>\n`;\n\n    const { descriptor } = parse(source);\n    const result = compileScript(descriptor);\n    // When implemented, withDefaults should merge default values\n    expect(result.code).toContain(\"default: 'default message'\");\n  });\n});\n"
  },
  {
    "path": "book/impls/60_basic_sfc_compiler/060_type_based_macros/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref, onMounted } from 'chibivue'\n\nconst count = ref(0)\nconst isClient = ref(false)\n\nonMounted(() => {\n  isClient.value = true\n})\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>SSR Example</h2>\n    <p>Count: {{ count }}</p>\n    <button @click=\"count++\">Increment</button>\n    <p v-if=\"isClient\">This content is only visible on the client (hydrated)</p>\n    <p v-else>This content is from SSR</p>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nexport * from \"./server-renderer\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n\n  // internal, for SSR\n  _component: Component;\n  _props: Record<string, any> | null;\n  _context: AppContext;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent, rootProps = null) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      _component: rootComponent,\n      _props: rootProps,\n      _context: context,\n\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, rootProps, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  attrs: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    attrs: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\nexport const setCurrentInstance = (instance: ComponentInternalInstance | null) => {\n  const prev = currentInstance;\n  currentInstance = instance;\n  if (instance) {\n    instance.scope.on();\n  }\n  return prev;\n};\n\nexport const unsetCurrentInstance = (prev: ComponentInternalInstance | null = null) => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = prev;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    const prev = setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance(prev);\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  const prev = setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance(prev);\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/hydration.ts",
    "content": "import { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport { type VNode, Text, Comment, Fragment, normalizeVNode } from \"./vnode\";\n\nexport interface HydrateOptions {\n  patchProp: (el: Element, key: string, prevValue: any, nextValue: any) => void;\n  nextSibling: (node: Node) => Node | null;\n}\n\nexport function createHydrationRenderer(options: HydrateOptions) {\n  const { patchProp, nextSibling } = options;\n\n  function hydrate(vnode: VNode, container: Element): void {\n    const node = container.firstChild;\n    if (node) {\n      hydrateNode(node, vnode, null);\n    }\n  }\n\n  function hydrateNode(\n    node: Node,\n    vnode: VNode,\n    parentComponent: ComponentInternalInstance | null,\n  ): Node | null {\n    const { type, shapeFlag } = vnode;\n    vnode.el = node;\n\n    if (type === Text) {\n      return nextSibling(node);\n    } else if (type === Comment) {\n      return nextSibling(node);\n    } else if (type === Fragment) {\n      return hydrateFragment(node, vnode, parentComponent);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      return hydrateElement(node as Element, vnode, parentComponent);\n    }\n\n    return nextSibling(node);\n  }\n\n  function hydrateElement(\n    el: Element,\n    vnode: VNode,\n    parentComponent: ComponentInternalInstance | null,\n  ): Node | null {\n    vnode.el = el;\n\n    const { props, children, shapeFlag } = vnode;\n\n    // Attach event handlers during hydration\n    if (props) {\n      for (const key in props) {\n        if (key.startsWith(\"on\") && typeof props[key] === \"function\") {\n          patchProp(el, key, null, props[key]);\n        }\n      }\n    }\n\n    // Hydrate children\n    if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      hydrateChildren(el.firstChild, children as VNode[], parentComponent);\n    }\n\n    return nextSibling(el);\n  }\n\n  function hydrateChildren(\n    node: Node | null,\n    children: VNode[],\n    parentComponent: ComponentInternalInstance | null,\n  ): Node | null {\n    for (let i = 0; i < children.length; i++) {\n      const child = normalizeVNode(children[i]);\n      if (node) {\n        node = hydrateNode(node, child, parentComponent);\n      }\n    }\n    return node;\n  }\n\n  function hydrateFragment(\n    node: Node,\n    vnode: VNode,\n    parentComponent: ComponentInternalInstance | null,\n  ): Node | null {\n    vnode.el = node;\n    let current = nextSibling(node);\n    const children = vnode.children as VNode[];\n\n    if (children && children.length > 0) {\n      current = hydrateChildren(current, children, parentComponent);\n    }\n\n    vnode.anchor = current;\n    return current ? nextSibling(current) : null;\n  }\n\n  return { hydrate };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport {\n  registerRuntimeCompiler,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  type InternalRenderFunction,\n  type ComponentInternalInstance,\n  type Component,\n} from \"./component\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport {\n  createVNode,\n  createCommentVNode,\n  normalizeVNode,\n  mergeProps,\n  isVNode,\n  Fragment,\n  Text,\n  Comment,\n  type VNode,\n  type VNodeProps,\n  type VNodeArrayChildren,\n  type DirectiveBinding,\n} from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n\nexport { createHydrationRenderer, type HydrateOptions } from \"./hydration\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode) => {\n    const { children, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountComponent(n2, container, anchor, parentComponent);\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  __v_isVNode: true;\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n\n  // directives\n  dirs?: DirectiveBinding[] | null;\n}\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentInternalInstance | null;\n  value: V;\n  oldValue: V | null;\n  arg?: string;\n  modifiers: Record<string, boolean>;\n  dir: ObjectDirective<any, V>;\n}\n\nexport interface ObjectDirective<T = any, V = any> {\n  created?: DirectiveHook<T, null, V>;\n  beforeMount?: DirectiveHook<T, null, V>;\n  mounted?: DirectiveHook<T, null, V>;\n  beforeUpdate?: DirectiveHook<T, VNode<T>, V>;\n  updated?: DirectiveHook<T, VNode<T>, V>;\n  beforeUnmount?: DirectiveHook<T, null, V>;\n  unmounted?: DirectiveHook<T, null, V>;\n  getSSRProps?: (binding: DirectiveBinding<V>, vnode: VNode) => Record<string, unknown> | undefined;\n}\n\nexport type DirectiveHook<T = any, Prev = VNode<T> | null, V = any> = (\n  el: T,\n  binding: DirectiveBinding<V>,\n  vnode: VNode<T>,\n  prevVNode: Prev,\n) => void;\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    __v_isVNode: true,\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n    dirs: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (child == null || typeof child === \"boolean\") {\n    return createVNode(Comment, null, \"\");\n  } else if (isArray(child)) {\n    return createVNode(Fragment, null, child.slice());\n  } else if (typeof child === \"object\") {\n    return cloneIfMounted(child as VNode);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nfunction cloneIfMounted(child: VNode): VNode {\n  return child.el === null ? child : ({ ...child, __v_isVNode: true } as VNode);\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function isVNode(value: any): value is VNode {\n  return value ? value.__v_isVNode === true : false;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/index.ts",
    "content": "import {\n  type CreateAppFunction,\n  createAppAPI,\n  createRenderer,\n  createHydrationRenderer,\n} from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\n// SSR Hydration support\nconst { hydrate: hydrateVNode } = createHydrationRenderer({\n  patchProp,\n  nextSibling: nodeOps.nextSibling,\n});\n\nexport const createSSRApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n\n  // Override mount to support hydration\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n\n    // Check if container has SSR content (has children)\n    if (container.hasChildNodes()) {\n      // Hydrate instead of replacing\n      const proxy = mount(container, true /* isHydrate */);\n      return proxy;\n    } else {\n      // No SSR content, do normal mount\n      mount(container);\n    }\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/helpers/ssrInterpolate.ts",
    "content": "import { toDisplayString } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrInterpolate(value: unknown): string {\n  return escapeHtml(toDisplayString(value));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/helpers/ssrRenderAttrs.ts",
    "content": "import { isArray, isFunction, isOn, isString, normalizeClass, normalizeStyle } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrRenderAttrs(props: Record<string, unknown>, tag?: string): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (ssrIsIgnoredKey(key) || isOn(key) || (tag === \"textarea\" && key === \"value\")) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return key === \"key\" || key === \"ref\" || key === \"innerHTML\" || key === \"textContent\";\n}\n\nexport function ssrRenderDynamicAttr(key: string, value: unknown, tag?: string): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag)) ? key : propsToAttrMap[key] || key.toLowerCase();\n\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\" ? ` ${attrKey}` : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(`[server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`);\n    return \"\";\n  }\n}\n\nexport function ssrRenderAttr(key: string, value: unknown): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  return ` ${key}=\"${escapeHtml(value)}\"`;\n}\n\nfunction isRenderableAttrValue(value: unknown): boolean {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(styles: Record<string, string | number> | null): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n\nfunction hyphenate(str: string): string {\n  return str.replace(/\\B([A-Z])/g, \"-$1\").toLowerCase();\n}\n\n// Maps props to their corresponding HTML attribute names\nconst propsToAttrMap: Record<string, string> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n\n// Boolean attributes\nconst isBooleanAttr = (key: string): boolean => booleanAttrsSet.has(key);\n\nconst booleanAttrsSet = new Set(\n  (\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n    \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n    \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n    \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n    \"required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"\n  ).split(\",\"),\n);\n\n// Checks if the attribute name is safe for SSR\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nfunction isSSRSafeAttrName(name: string): boolean {\n  return !unsafeAttrCharRE.test(name);\n}\n\n// SVG tags\nconst SVG_TAGS = new Set(\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\".split(\n    \",\",\n  ),\n);\n\nfunction isSVGTag(tag: string): boolean {\n  return SVG_TAGS.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/helpers/ssrRenderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\n\nexport function ssrRenderList(\n  source: unknown,\n  renderItem: (value: unknown, key: string | number, index?: number) => void,\n): void {\n  if (isArray(source) || isString(source)) {\n    for (let i = 0, l = source.length; i < l; i++) {\n      renderItem(source[i], i);\n    }\n  } else if (typeof source === \"number\") {\n    for (let i = 0; i < source; i++) {\n      renderItem(i + 1, i);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      const arr = Array.from(source as Iterable<any>);\n      for (let i = 0, l = arr.length; i < l; i++) {\n        renderItem(arr[i], i);\n      }\n    } else {\n      const keys = Object.keys(source);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        renderItem((source as Record<string, unknown>)[key], key, i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/helpers/ssrUtils.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n\n// void elements\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nconst isVoidTagSet = new Set(VOID_TAGS.split(\",\"));\nexport function isVoidTag(tag: string): boolean {\n  return isVoidTagSet.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/index.ts",
    "content": "// public\nexport type { SSRContext } from \"./render\";\nexport { renderToString } from \"./renderToString\";\n\n// internal runtime helpers\nexport { ssrInterpolate } from \"./helpers/ssrInterpolate\";\nexport { ssrRenderList } from \"./helpers/ssrRenderList\";\nexport {\n  ssrRenderAttrs,\n  ssrRenderClass,\n  ssrRenderStyle,\n  ssrRenderAttr,\n  ssrRenderDynamicAttr,\n} from \"./helpers/ssrRenderAttrs\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/render.ts",
    "content": "import {\n  Comment,\n  type Component,\n  type ComponentInternalInstance,\n  type DirectiveBinding,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  type VNodeProps,\n  mergeProps,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  normalizeVNode,\n} from \"../runtime-core\";\nimport { ShapeFlags, isArray, isFunction, isPromise, isString } from \"../shared\";\nimport { ssrRenderAttrs } from \"./helpers/ssrRenderAttrs\";\nimport { escapeHtml, escapeHtmlComment, isVoidTag } from \"./helpers/ssrUtils\";\n\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\nexport type Props = Record<string, unknown>;\n\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  /**\n   * @internal\n   */\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  /**\n   * @internal\n   */\n  __watcherHandles?: (() => void)[];\n};\n\n// Each component has a buffer array.\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = (vnode.component = createComponentInstance(vnode, parentComponent, null as any));\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() => renderComponentSubTree(instance));\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    const root = (comp as Function)(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`);\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      }\n  }\n}\n\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const {\n      dir: { getSSRProps },\n    } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/server-renderer/renderToString.ts",
    "content": "import { type App, type VNode, createVNode, isVNode } from \"../runtime-core\";\nimport { isPromise, isString } from \"../shared\";\nimport { type SSRBuffer, type SSRContext, renderComponentVNode } from \"./render\";\n\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // raw vnode, wrap with app\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }, null, null),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // rendering an app\n  const app = input;\n  const vnode = createVNode(app._component, app._props, null);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n\nexport const isOn = (key: string) =>\n  key.charCodeAt(0) === 111 /* o */ &&\n  key.charCodeAt(1) === 110 /* n */ &&\n  (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return (\n    (isObject(val) || isFunction(val)) &&\n    isFunction((val as any).then) &&\n    isFunction((val as any).catch)\n  );\n};\n\nexport const toDisplayString = (val: unknown): string => {\n  return isString(val)\n    ? val\n    : val == null\n      ? \"\"\n      : isArray(val) ||\n          (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))\n        ? JSON.stringify(val, null, 2)\n        : String(val);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\nexport * from \"./shapeFlags\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\nimport { createApp, createSSRApp, h, renderToString, ref } from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"90_web_application_essentials/010_ssr\", () => {\n  it(\"should render to string\", async () => {\n    const app = createApp({\n      template: `<div>Hello SSR!</div>`,\n    });\n\n    const html = await renderToString(app);\n    // Single root element, but still wrapped in fragment comments by the template compiler\n    expect(html).toContain(\"<div>Hello SSR!</div>\");\n  });\n\n  it(\"should escape HTML in text content\", async () => {\n    const app = createApp({\n      template: `<div>{{ text }}</div>`,\n      setup() {\n        return { text: \"<script>alert('xss')</script>\" };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;\");\n  });\n\n  it(\"should render with class and style bindings\", async () => {\n    const app = createApp({\n      template: `<div :class=\"cls\" :style=\"styles\">content</div>`,\n      setup() {\n        return {\n          cls: { active: true, disabled: false },\n          styles: { color: \"red\", fontSize: \"14px\" },\n        };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain('class=\"active\"');\n    expect(html).toContain('style=\"color:red;font-size:14px;\"');\n  });\n\n  it(\"should render fragment\", async () => {\n    const app = createApp({\n      template: `<p>one</p><p>two</p>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<p>one</p><p>two</p>\");\n  });\n\n  it(\"should render nested components\", async () => {\n    const Child = {\n      props: [\"name\"],\n      template: `<span>{{ name }}</span>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<div><Child name=\"test\" /></div>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<span>\");\n    expect(html).toContain(\"</span>\");\n    expect(html).toContain(\"<div>\");\n  });\n\n  it(\"should render with h function\", async () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", { class: \"test\" }, \"Hello from h()\");\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toBe('<div class=\"test\">Hello from h()</div>');\n  });\n});\n\ndescribe(\"90_web_application_essentials/010_ssr - hydration\", () => {\n  it(\"should create SSR app\", () => {\n    const app = createSSRApp({\n      template: `<div>Hello SSR App!</div>`,\n    });\n\n    expect(app).toBeDefined();\n    expect(app.mount).toBeDefined();\n  });\n\n  it(\"should hydrate SSR rendered content\", async () => {\n    // First, render on server\n    const serverApp = createApp({\n      template: `<div>Hydrated content</div>`,\n    });\n    const html = await renderToString(serverApp);\n\n    // Set up host with SSR content\n    host.innerHTML = html;\n\n    // Then hydrate on client\n    const clientApp = createSSRApp({\n      template: `<div>Hydrated content</div>`,\n    });\n    clientApp.mount(\"#host\");\n\n    // Content should still be there\n    expect(host.innerHTML).toContain(\"Hydrated content\");\n  });\n\n  it(\"should hydrate content with dynamic data\", async () => {\n    // Server render\n    const serverApp = createApp({\n      setup() {\n        return { msg: \"Hello from SSR\" };\n      },\n      template: `<div>{{ msg }}</div>`,\n    });\n    const html = await renderToString(serverApp);\n    host.innerHTML = html;\n\n    // Client hydrate\n    const clientApp = createSSRApp({\n      setup() {\n        return { msg: \"Hello from SSR\" };\n      },\n      template: `<div>{{ msg }}</div>`,\n    });\n    clientApp.mount(\"#host\");\n\n    expect(host.innerHTML).toContain(\"Hello from SSR\");\n  });\n\n  it(\"should hydrate nested elements\", async () => {\n    const serverApp = createApp({\n      template: `<div><span>nested</span><p>content</p></div>`,\n    });\n    const html = await renderToString(serverApp);\n    host.innerHTML = html;\n\n    const clientApp = createSSRApp({\n      template: `<div><span>nested</span><p>content</p></div>`,\n    });\n    clientApp.mount(\"#host\");\n\n    expect(host.innerHTML).toContain(\"<span>nested</span>\");\n    expect(host.innerHTML).toContain(\"<p>content</p>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/010_ssr/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref, KeepAlive } from 'chibivue'\nimport CounterComponent from './Counter.vue'\n\nconst showCounter = ref(true)\nconst toggle = () => {\n  showCounter.value = !showCounter.value\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>KeepAlive Example</h2>\n    <button @click=\"toggle\">Toggle Counter</button>\n\n    <h3>With KeepAlive (state preserved):</h3>\n    <KeepAlive>\n      <CounterComponent v-if=\"showCounter\" />\n    </KeepAlive>\n\n    <h3>Without KeepAlive (state reset):</h3>\n    <CounterComponent v-if=\"showCounter\" />\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/src/Counter.vue",
    "content": "<script setup>\nimport { ref, onActivated, onDeactivated } from 'chibivue'\n\nconst count = ref(0)\nconst status = ref('active')\n\nonActivated(() => {\n  status.value = 'activated'\n  console.log('Counter activated')\n})\n\nonDeactivated(() => {\n  status.value = 'deactivated'\n  console.log('Counter deactivated')\n})\n</script>\n\n<template>\n  <div class=\"counter\">\n    <p>Count: {{ count }}</p>\n    <p>Status: {{ status }}</p>\n    <button @click=\"count++\">Increment</button>\n  </div>\n</template>\n\n<style>\n.counter {\n  padding: 16px;\n  background-color: #f0f0f0;\n  border-radius: 4px;\n  margin: 8px 0;\n}\n</style>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nexport * from \"./server-renderer\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n\n  // internal, for SSR\n  _component: Component;\n  _props: Record<string, any> | null;\n  _context: AppContext;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent, rootProps = null) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      _component: rootComponent,\n      _props: rootProps,\n      _context: context,\n\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, rootProps, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n\n// KeepAlive lifecycle hooks\nexport const onActivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.a || (target.a = []);\n    hooks.push(hook);\n  }\n};\n\nexport const onDeactivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.da || (target.da = []);\n    hooks.push(hook);\n  }\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  attrs: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  isDeactivated: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n\n  // KeepAlive lifecycle hooks\n  a: LifecycleHook; // activated\n  da: LifecycleHook; // deactivated\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    attrs: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    isDeactivated: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n\n    // KeepAlive lifecycle hooks\n    a: null,\n    da: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\n\nexport const getCurrentInstance = (): ComponentInternalInstance | null => {\n  return currentInstance;\n};\n\nexport const setCurrentInstance = (instance: ComponentInternalInstance | null) => {\n  const prev = currentInstance;\n  currentInstance = instance;\n  if (instance) {\n    instance.scope.on();\n  }\n  return prev;\n};\n\nexport const unsetCurrentInstance = (prev: ComponentInternalInstance | null = null) => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = prev;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    const prev = setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance(prev);\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  const prev = setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance(prev);\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/components/KeepAlive.ts",
    "content": "import { ShapeFlags, isArray, isString } from \"../../shared\";\nimport type { ComponentInternalInstance, Data } from \"../component\";\nimport { getCurrentInstance } from \"../component\";\nimport type { VNode } from \"../vnode\";\nimport { isSameVNodeType } from \"../vnode\";\nimport { queuePostFlushCb } from \"../scheduler\";\n\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n\ninterface KeepAliveRenderer {\n  p: (\n    n1: VNode | null,\n    n2: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  m: (vnode: VNode, container: any, anchor: any | null) => void;\n  um: (vnode: VNode) => void;\n  o: {\n    createElement: (type: string) => any;\n  };\n}\n\nconst KeepAliveImpl = {\n  name: `KeepAlive`,\n  __isKeepAlive: true,\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number],\n  },\n  setup(props: KeepAliveProps, { slots }: { slots: any }) {\n    const instance = getCurrentInstance()! as KeepAliveContext;\n    const cache: Map<any, VNode> = new Map();\n    const keys: Set<any> = new Set();\n    let current: VNode | null = null;\n\n    const parentComponent = instance.parent as ComponentInternalInstance;\n\n    // Create a hidden container for holding deactivated components\n    const storageContainer = instance.renderer.o.createElement(\"div\");\n\n    instance.activate = (vnode, container, anchor, _parentComponent) => {\n      const instance = vnode.component!;\n      move(vnode, container, anchor);\n      // in case props have changed\n      patch(instance.vnode, vnode, container, anchor, parentComponent);\n      queuePostFlushCb(() => {\n        instance.isDeactivated = false;\n        if (instance.a) {\n          instance.a.forEach((hook: () => void) => hook());\n        }\n      });\n    };\n\n    instance.deactivate = (vnode: VNode) => {\n      move(vnode, storageContainer, null);\n      queuePostFlushCb(() => {\n        const instance = vnode.component!;\n        if (instance.da) {\n          instance.da.forEach((hook: () => void) => hook());\n        }\n        instance.isDeactivated = true;\n      });\n    };\n\n    function move(vnode: VNode, container: any, anchor: any | null): void {\n      instance.renderer.m(vnode, container, anchor);\n    }\n\n    function patch(\n      n1: VNode | null,\n      n2: VNode,\n      container: any,\n      anchor: any | null,\n      parentComponent: ComponentInternalInstance | null,\n    ): void {\n      instance.renderer.p(n1, n2, container, anchor, parentComponent);\n    }\n\n    function unmount(vnode: VNode): void {\n      resetShapeFlag(vnode);\n      instance.renderer.um(vnode);\n    }\n\n    function pruneCacheEntry(key: any): void {\n      const cached = cache.get(key) as VNode;\n      if (!current || !isSameVNodeType(cached, current)) {\n        unmount(cached);\n      } else if (current) {\n        resetShapeFlag(current);\n      }\n      cache.delete(key);\n      keys.delete(key);\n    }\n\n    return (): VNode | undefined => {\n      if (!slots.default) {\n        return undefined;\n      }\n\n      const children = slots.default();\n      const rawVNode = children[0];\n      if (children.length > 1) {\n        current = null;\n        return children as unknown as VNode;\n      } else if (\n        !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n        !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n      ) {\n        current = null;\n        return rawVNode;\n      }\n\n      let vnode = rawVNode;\n      const comp = vnode.type as any;\n      const name = getComponentName(comp);\n      const { include, exclude, max } = props;\n\n      if (\n        (include && (!name || !matches(include, name))) ||\n        (exclude && name && matches(exclude, name))\n      ) {\n        current = vnode;\n        return rawVNode;\n      }\n\n      const key = vnode.key == null ? comp : vnode.key;\n      const cachedVNode = cache.get(key);\n\n      if (cachedVNode) {\n        vnode.el = cachedVNode.el;\n        vnode.component = cachedVNode.component;\n        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n        keys.delete(key);\n        keys.add(key);\n      } else {\n        keys.add(key);\n        if (max && keys.size > parseInt(max as string, 10)) {\n          pruneCacheEntry(keys.values().next().value);\n        }\n      }\n\n      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n      current = vnode;\n      return vnode;\n    };\n  },\n};\n\nexport const KeepAlive = KeepAliveImpl as any as {\n  __isKeepAlive: true;\n  new (): {\n    $props: KeepAliveProps;\n  };\n};\n\nexport function isKeepAlive(vnode: VNode): boolean {\n  return (vnode.type as any).__isKeepAlive === true;\n}\n\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n\nfunction getComponentName(comp: { name?: string; __name?: string }): string | undefined {\n  return comp.name || comp.__name;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport {\n  registerRuntimeCompiler,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  getCurrentInstance,\n  type InternalRenderFunction,\n  type ComponentInternalInstance,\n  type Component,\n} from \"./component\";\n\nexport { KeepAlive } from \"./components/KeepAlive\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  onActivated,\n  onDeactivated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport {\n  createVNode,\n  createCommentVNode,\n  normalizeVNode,\n  mergeProps,\n  isVNode,\n  Fragment,\n  Text,\n  Comment,\n  type VNode,\n  type VNodeProps,\n  type VNodeArrayChildren,\n  type DirectiveBinding,\n} from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { type KeepAliveContext, isKeepAlive } from \"./components/KeepAlive\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode, parentComponent?: ComponentInternalInstance | null) => {\n    const { children, shapeFlag } = vnode;\n\n    // KeepAlive: deactivate instead of unmount\n    if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n      (parentComponent as KeepAliveContext).deactivate(vnode);\n      return;\n    }\n\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n        // Restore from cache\n        (parentComponent as KeepAliveContext).activate(n2, container, anchor, parentComponent);\n      } else {\n        mountComponent(n2, container, anchor, parentComponent);\n      }\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n\n    // Inject renderer internals for KeepAlive\n    if (isKeepAlive(initialVNode)) {\n      (instance as KeepAliveContext).renderer = {\n        p: patch,\n        m: move,\n        um: unmount,\n        o: {\n          createElement: hostCreateElement,\n        },\n      };\n    }\n\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface VNode<HostNode = any> {\n  __v_isVNode: true;\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n\n  // directives\n  dirs?: DirectiveBinding[] | null;\n}\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentInternalInstance | null;\n  value: V;\n  oldValue: V | null;\n  arg?: string;\n  modifiers: Record<string, boolean>;\n  dir: ObjectDirective<any, V>;\n}\n\nexport interface ObjectDirective<T = any, V = any> {\n  created?: DirectiveHook<T, null, V>;\n  beforeMount?: DirectiveHook<T, null, V>;\n  mounted?: DirectiveHook<T, null, V>;\n  beforeUpdate?: DirectiveHook<T, VNode<T>, V>;\n  updated?: DirectiveHook<T, VNode<T>, V>;\n  beforeUnmount?: DirectiveHook<T, null, V>;\n  unmounted?: DirectiveHook<T, null, V>;\n  getSSRProps?: (binding: DirectiveBinding<V>, vnode: VNode) => Record<string, unknown> | undefined;\n}\n\nexport type DirectiveHook<T = any, Prev = VNode<T> | null, V = any> = (\n  el: T,\n  binding: DirectiveBinding<V>,\n  vnode: VNode<T>,\n  prevVNode: Prev,\n) => void;\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    __v_isVNode: true,\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n    dirs: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (child == null || typeof child === \"boolean\") {\n    return createVNode(Comment, null, \"\");\n  } else if (isArray(child)) {\n    return createVNode(Fragment, null, child.slice());\n  } else if (typeof child === \"object\") {\n    return cloneIfMounted(child as VNode);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nfunction cloneIfMounted(child: VNode): VNode {\n  return child.el === null ? child : ({ ...child, __v_isVNode: true } as VNode);\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function isVNode(value: any): value is VNode {\n  return value ? value.__v_isVNode === true : false;\n}\n\nexport function cloneVNode(vnode: VNode, extraProps?: VNodeProps | null): VNode {\n  const { props, children } = vnode;\n  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;\n  const cloned: VNode = {\n    __v_isVNode: true,\n    type: vnode.type,\n    props: mergedProps,\n    key: mergedProps?.key ?? vnode.key,\n    ref: mergedProps?.ref ?? vnode.ref,\n    children: children,\n    component: vnode.component,\n    el: vnode.el,\n    anchor: vnode.anchor,\n    shapeFlag: vnode.shapeFlag,\n    appContext: vnode.appContext,\n    dirs: vnode.dirs,\n  };\n  return cloned;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/helpers/ssrInterpolate.ts",
    "content": "import { toDisplayString } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrInterpolate(value: unknown): string {\n  return escapeHtml(toDisplayString(value));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/helpers/ssrRenderAttrs.ts",
    "content": "import { isArray, isFunction, isOn, isString, normalizeClass, normalizeStyle } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrRenderAttrs(props: Record<string, unknown>, tag?: string): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (ssrIsIgnoredKey(key) || isOn(key) || (tag === \"textarea\" && key === \"value\")) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return key === \"key\" || key === \"ref\" || key === \"innerHTML\" || key === \"textContent\";\n}\n\nexport function ssrRenderDynamicAttr(key: string, value: unknown, tag?: string): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag)) ? key : propsToAttrMap[key] || key.toLowerCase();\n\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\" ? ` ${attrKey}` : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(`[server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`);\n    return \"\";\n  }\n}\n\nexport function ssrRenderAttr(key: string, value: unknown): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  return ` ${key}=\"${escapeHtml(value)}\"`;\n}\n\nfunction isRenderableAttrValue(value: unknown): boolean {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(styles: Record<string, string | number> | null): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n\nfunction hyphenate(str: string): string {\n  return str.replace(/\\B([A-Z])/g, \"-$1\").toLowerCase();\n}\n\n// Maps props to their corresponding HTML attribute names\nconst propsToAttrMap: Record<string, string> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n\n// Boolean attributes\nconst isBooleanAttr = (key: string): boolean => booleanAttrsSet.has(key);\n\nconst booleanAttrsSet = new Set(\n  (\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n    \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n    \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n    \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n    \"required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"\n  ).split(\",\"),\n);\n\n// Checks if the attribute name is safe for SSR\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nfunction isSSRSafeAttrName(name: string): boolean {\n  return !unsafeAttrCharRE.test(name);\n}\n\n// SVG tags\nconst SVG_TAGS = new Set(\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\".split(\n    \",\",\n  ),\n);\n\nfunction isSVGTag(tag: string): boolean {\n  return SVG_TAGS.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/helpers/ssrRenderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\n\nexport function ssrRenderList(\n  source: unknown,\n  renderItem: (value: unknown, key: string | number, index?: number) => void,\n): void {\n  if (isArray(source) || isString(source)) {\n    for (let i = 0, l = source.length; i < l; i++) {\n      renderItem(source[i], i);\n    }\n  } else if (typeof source === \"number\") {\n    for (let i = 0; i < source; i++) {\n      renderItem(i + 1, i);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      const arr = Array.from(source as Iterable<any>);\n      for (let i = 0, l = arr.length; i < l; i++) {\n        renderItem(arr[i], i);\n      }\n    } else {\n      const keys = Object.keys(source);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        renderItem((source as Record<string, unknown>)[key], key, i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/helpers/ssrUtils.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n\n// void elements\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nconst isVoidTagSet = new Set(VOID_TAGS.split(\",\"));\nexport function isVoidTag(tag: string): boolean {\n  return isVoidTagSet.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/index.ts",
    "content": "// public\nexport type { SSRContext } from \"./render\";\nexport { renderToString } from \"./renderToString\";\n\n// internal runtime helpers\nexport { ssrInterpolate } from \"./helpers/ssrInterpolate\";\nexport { ssrRenderList } from \"./helpers/ssrRenderList\";\nexport {\n  ssrRenderAttrs,\n  ssrRenderClass,\n  ssrRenderStyle,\n  ssrRenderAttr,\n  ssrRenderDynamicAttr,\n} from \"./helpers/ssrRenderAttrs\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/render.ts",
    "content": "import {\n  Comment,\n  type Component,\n  type ComponentInternalInstance,\n  type DirectiveBinding,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  type VNodeProps,\n  mergeProps,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  normalizeVNode,\n} from \"../runtime-core\";\nimport { ShapeFlags, isArray, isFunction, isPromise, isString } from \"../shared\";\nimport { ssrRenderAttrs } from \"./helpers/ssrRenderAttrs\";\nimport { escapeHtml, escapeHtmlComment, isVoidTag } from \"./helpers/ssrUtils\";\n\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\nexport type Props = Record<string, unknown>;\n\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  /**\n   * @internal\n   */\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  /**\n   * @internal\n   */\n  __watcherHandles?: (() => void)[];\n};\n\n// Each component has a buffer array.\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = (vnode.component = createComponentInstance(vnode, parentComponent, null as any));\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() => renderComponentSubTree(instance));\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    const root = (comp as Function)(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`);\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      }\n  }\n}\n\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const {\n      dir: { getSSRProps },\n    } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/server-renderer/renderToString.ts",
    "content": "import { type App, type VNode, createVNode, isVNode } from \"../runtime-core\";\nimport { isPromise, isString } from \"../shared\";\nimport { type SSRBuffer, type SSRContext, renderComponentVNode } from \"./render\";\n\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // raw vnode, wrap with app\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }, null, null),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // rendering an app\n  const app = input;\n  const vnode = createVNode(app._component, app._props, null);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n\nexport const isOn = (key: string) =>\n  key.charCodeAt(0) === 111 /* o */ &&\n  key.charCodeAt(1) === 110 /* n */ &&\n  (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return (\n    (isObject(val) || isFunction(val)) &&\n    isFunction((val as any).then) &&\n    isFunction((val as any).catch)\n  );\n};\n\nexport const toDisplayString = (val: unknown): string => {\n  return isString(val)\n    ? val\n    : val == null\n      ? \"\"\n      : isArray(val) ||\n          (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))\n        ? JSON.stringify(val, null, 2)\n        : String(val);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\nexport * from \"./shapeFlags\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  STATEFUL_COMPONENT = 1 << 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,\n  COMPONENT_KEPT_ALIVE = 1 << 9,\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport {\n  createApp,\n  h,\n  renderToString,\n  KeepAlive,\n  onActivated,\n  onDeactivated,\n  ref,\n} from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"90_web_application_essentials/010_ssr\", () => {\n  it(\"should render to string\", async () => {\n    const app = createApp({\n      template: `<div>Hello SSR!</div>`,\n    });\n\n    const html = await renderToString(app);\n    // Single root element, but still wrapped in fragment comments by the template compiler\n    expect(html).toContain(\"<div>Hello SSR!</div>\");\n  });\n\n  it(\"should escape HTML in text content\", async () => {\n    const app = createApp({\n      template: `<div>{{ text }}</div>`,\n      setup() {\n        return { text: \"<script>alert('xss')</script>\" };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;\");\n  });\n\n  it(\"should render with class and style bindings\", async () => {\n    const app = createApp({\n      template: `<div :class=\"cls\" :style=\"styles\">content</div>`,\n      setup() {\n        return {\n          cls: { active: true, disabled: false },\n          styles: { color: \"red\", fontSize: \"14px\" },\n        };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain('class=\"active\"');\n    expect(html).toContain('style=\"color:red;font-size:14px;\"');\n  });\n\n  it(\"should render fragment\", async () => {\n    const app = createApp({\n      template: `<p>one</p><p>two</p>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<p>one</p><p>two</p>\");\n  });\n\n  it(\"should render nested components\", async () => {\n    const Child = {\n      props: [\"name\"],\n      template: `<span>{{ name }}</span>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<div><Child name=\"test\" /></div>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<span>\");\n    expect(html).toContain(\"</span>\");\n    expect(html).toContain(\"<div>\");\n  });\n\n  it(\"should render with h function\", async () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", { class: \"test\" }, \"Hello from h()\");\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toBe('<div class=\"test\">Hello from h()</div>');\n  });\n});\n\ndescribe(\"90_web_application_essentials/020_keep_alive\", () => {\n  it(\"should render KeepAlive wrapper\", () => {\n    const Child = {\n      name: \"Child\",\n      template: `<div>child content</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child content\");\n  });\n\n  it(\"should set COMPONENT_SHOULD_KEEP_ALIVE flag\", () => {\n    const Child = {\n      name: \"Child\",\n      render() {\n        return h(\"div\", null, \"child\");\n      },\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n  });\n\n  it(\"should render with activated/deactivated hooks registered\", () => {\n    let activated = false;\n    let deactivated = false;\n\n    const Child = {\n      name: \"Child\",\n      setup() {\n        onActivated(() => {\n          activated = true;\n        });\n        onDeactivated(() => {\n          deactivated = true;\n        });\n        return {};\n      },\n      template: `<div>child</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n    // Hooks are registered but not yet called\n    expect(activated).toBe(false);\n    expect(deactivated).toBe(false);\n  });\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/020_keep_alive/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref, Transition } from 'chibivue'\n\nconst show = ref(true)\nconst toggle = () => {\n  show.value = !show.value\n}\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Transition Example</h2>\n    <button @click=\"toggle\">Toggle</button>\n\n    <Transition name=\"fade\">\n      <div v-if=\"show\" class=\"box\">\n        This content will fade in/out\n      </div>\n    </Transition>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n.box {\n  padding: 16px;\n  background-color: #42b883;\n  color: white;\n  margin-top: 16px;\n  border-radius: 4px;\n}\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.3s ease;\n}\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context); // NOTE: 将来的には関数の外に出す\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type TemplateChildNode,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nexport * from \"./server-renderer\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n\n  // internal, for SSR\n  _component: Component;\n  _props: Record<string, any> | null;\n  _context: AppContext;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent, rootProps = null) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      _component: rootComponent,\n      _props: rootProps,\n      _context: context,\n\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, rootProps, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n\n// KeepAlive lifecycle hooks\nexport const onActivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.a || (target.a = []);\n    hooks.push(hook);\n  }\n};\n\nexport const onDeactivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.da || (target.da = []);\n    hooks.push(hook);\n  }\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  attrs: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  isDeactivated: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n\n  // KeepAlive lifecycle hooks\n  a: LifecycleHook; // activated\n  da: LifecycleHook; // deactivated\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    attrs: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    isDeactivated: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n\n    // KeepAlive lifecycle hooks\n    a: null,\n    da: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\n\nexport const getCurrentInstance = (): ComponentInternalInstance | null => {\n  return currentInstance;\n};\n\nexport const setCurrentInstance = (instance: ComponentInternalInstance | null) => {\n  const prev = currentInstance;\n  currentInstance = instance;\n  if (instance) {\n    instance.scope.on();\n  }\n  return prev;\n};\n\nexport const unsetCurrentInstance = (prev: ComponentInternalInstance | null = null) => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = prev;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    const prev = setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance(prev);\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  const prev = setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance(prev);\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/components/KeepAlive.ts",
    "content": "import { ShapeFlags, isArray, isString } from \"../../shared\";\nimport type { ComponentInternalInstance, Data } from \"../component\";\nimport { getCurrentInstance } from \"../component\";\nimport type { VNode } from \"../vnode\";\nimport { isSameVNodeType } from \"../vnode\";\nimport { queuePostFlushCb } from \"../scheduler\";\n\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n\ninterface KeepAliveRenderer {\n  p: (\n    n1: VNode | null,\n    n2: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  m: (vnode: VNode, container: any, anchor: any | null) => void;\n  um: (vnode: VNode) => void;\n  o: {\n    createElement: (type: string) => any;\n  };\n}\n\nconst KeepAliveImpl = {\n  name: `KeepAlive`,\n  __isKeepAlive: true,\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number],\n  },\n  setup(props: KeepAliveProps, { slots }: { slots: any }) {\n    const instance = getCurrentInstance()! as KeepAliveContext;\n    const cache: Map<any, VNode> = new Map();\n    const keys: Set<any> = new Set();\n    let current: VNode | null = null;\n\n    const parentComponent = instance.parent as ComponentInternalInstance;\n\n    // Create a hidden container for holding deactivated components\n    const storageContainer = instance.renderer.o.createElement(\"div\");\n\n    instance.activate = (vnode, container, anchor, _parentComponent) => {\n      const instance = vnode.component!;\n      move(vnode, container, anchor);\n      // in case props have changed\n      patch(instance.vnode, vnode, container, anchor, parentComponent);\n      queuePostFlushCb(() => {\n        instance.isDeactivated = false;\n        if (instance.a) {\n          instance.a.forEach((hook: () => void) => hook());\n        }\n      });\n    };\n\n    instance.deactivate = (vnode: VNode) => {\n      move(vnode, storageContainer, null);\n      queuePostFlushCb(() => {\n        const instance = vnode.component!;\n        if (instance.da) {\n          instance.da.forEach((hook: () => void) => hook());\n        }\n        instance.isDeactivated = true;\n      });\n    };\n\n    function move(vnode: VNode, container: any, anchor: any | null): void {\n      instance.renderer.m(vnode, container, anchor);\n    }\n\n    function patch(\n      n1: VNode | null,\n      n2: VNode,\n      container: any,\n      anchor: any | null,\n      parentComponent: ComponentInternalInstance | null,\n    ): void {\n      instance.renderer.p(n1, n2, container, anchor, parentComponent);\n    }\n\n    function unmount(vnode: VNode): void {\n      resetShapeFlag(vnode);\n      instance.renderer.um(vnode);\n    }\n\n    function pruneCacheEntry(key: any): void {\n      const cached = cache.get(key) as VNode;\n      if (!current || !isSameVNodeType(cached, current)) {\n        unmount(cached);\n      } else if (current) {\n        resetShapeFlag(current);\n      }\n      cache.delete(key);\n      keys.delete(key);\n    }\n\n    return (): VNode | undefined => {\n      if (!slots.default) {\n        return undefined;\n      }\n\n      const children = slots.default();\n      const rawVNode = children[0];\n      if (children.length > 1) {\n        current = null;\n        return children as unknown as VNode;\n      } else if (\n        !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n        !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n      ) {\n        current = null;\n        return rawVNode;\n      }\n\n      let vnode = rawVNode;\n      const comp = vnode.type as any;\n      const name = getComponentName(comp);\n      const { include, exclude, max } = props;\n\n      if (\n        (include && (!name || !matches(include, name))) ||\n        (exclude && name && matches(exclude, name))\n      ) {\n        current = vnode;\n        return rawVNode;\n      }\n\n      const key = vnode.key == null ? comp : vnode.key;\n      const cachedVNode = cache.get(key);\n\n      if (cachedVNode) {\n        vnode.el = cachedVNode.el;\n        vnode.component = cachedVNode.component;\n        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n        keys.delete(key);\n        keys.add(key);\n      } else {\n        keys.add(key);\n        if (max && keys.size > parseInt(max as string, 10)) {\n          pruneCacheEntry(keys.values().next().value);\n        }\n      }\n\n      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n      current = vnode;\n      return vnode;\n    };\n  },\n};\n\nexport const KeepAlive = KeepAliveImpl as any as {\n  __isKeepAlive: true;\n  new (): {\n    $props: KeepAliveProps;\n  };\n};\n\nexport function isKeepAlive(vnode: VNode): boolean {\n  return (vnode.type as any).__isKeepAlive === true;\n}\n\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n\nfunction getComponentName(comp: { name?: string; __name?: string }): string | undefined {\n  return comp.name || comp.__name;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport {\n  registerRuntimeCompiler,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  getCurrentInstance,\n  type InternalRenderFunction,\n  type ComponentInternalInstance,\n  type Component,\n} from \"./component\";\n\nexport { KeepAlive } from \"./components/KeepAlive\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  onActivated,\n  onDeactivated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport {\n  createVNode,\n  createCommentVNode,\n  normalizeVNode,\n  mergeProps,\n  isVNode,\n  Fragment,\n  Text,\n  Comment,\n  type VNode,\n  type VNodeProps,\n  type VNodeArrayChildren,\n  type DirectiveBinding,\n} from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { type KeepAliveContext, isKeepAlive } from \"./components/KeepAlive\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode, parentComponent?: ComponentInternalInstance | null) => {\n    const { children, shapeFlag } = vnode;\n\n    // KeepAlive: deactivate instead of unmount\n    if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n      (parentComponent as KeepAliveContext).deactivate(vnode);\n      return;\n    }\n\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n        // Restore from cache\n        (parentComponent as KeepAliveContext).activate(n2, container, anchor, parentComponent);\n      } else {\n        mountComponent(n2, container, anchor, parentComponent);\n      }\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n\n    // Inject renderer internals for KeepAlive\n    if (isKeepAlive(initialVNode)) {\n      (instance as KeepAliveContext).renderer = {\n        p: patch,\n        m: move,\n        um: unmount,\n        o: {\n          createElement: hostCreateElement,\n        },\n      };\n    }\n\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface TransitionHooks<HostElement = any> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nexport interface VNode<HostNode = any> {\n  __v_isVNode: true;\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n\n  // directives\n  dirs?: DirectiveBinding[] | null;\n\n  // transition hooks\n  transition?: TransitionHooks<HostNode> | null;\n}\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentInternalInstance | null;\n  value: V;\n  oldValue: V | null;\n  arg?: string;\n  modifiers: Record<string, boolean>;\n  dir: ObjectDirective<any, V>;\n}\n\nexport interface ObjectDirective<T = any, V = any> {\n  created?: DirectiveHook<T, null, V>;\n  beforeMount?: DirectiveHook<T, null, V>;\n  mounted?: DirectiveHook<T, null, V>;\n  beforeUpdate?: DirectiveHook<T, VNode<T>, V>;\n  updated?: DirectiveHook<T, VNode<T>, V>;\n  beforeUnmount?: DirectiveHook<T, null, V>;\n  unmounted?: DirectiveHook<T, null, V>;\n  getSSRProps?: (binding: DirectiveBinding<V>, vnode: VNode) => Record<string, unknown> | undefined;\n}\n\nexport type DirectiveHook<T = any, Prev = VNode<T> | null, V = any> = (\n  el: T,\n  binding: DirectiveBinding<V>,\n  vnode: VNode<T>,\n  prevVNode: Prev,\n) => void;\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    __v_isVNode: true,\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n    dirs: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (child == null || typeof child === \"boolean\") {\n    return createVNode(Comment, null, \"\");\n  } else if (isArray(child)) {\n    return createVNode(Fragment, null, child.slice());\n  } else if (typeof child === \"object\") {\n    return cloneIfMounted(child as VNode);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nfunction cloneIfMounted(child: VNode): VNode {\n  return child.el === null ? child : ({ ...child, __v_isVNode: true } as VNode);\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function isVNode(value: any): value is VNode {\n  return value ? value.__v_isVNode === true : false;\n}\n\nexport function cloneVNode(vnode: VNode, extraProps?: VNodeProps | null): VNode {\n  const { props, children } = vnode;\n  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;\n  const cloned: VNode = {\n    __v_isVNode: true,\n    type: vnode.type,\n    props: mergedProps,\n    key: mergedProps?.key ?? vnode.key,\n    ref: mergedProps?.ref ?? vnode.ref,\n    children: children,\n    component: vnode.component,\n    el: vnode.el,\n    anchor: vnode.anchor,\n    shapeFlag: vnode.shapeFlag,\n    appContext: vnode.appContext,\n    dirs: vnode.dirs,\n  };\n  return cloned;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/components/Transition.ts",
    "content": "import type { VNode } from \"../../runtime-core\";\n\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n}\n\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nconst TRANSITION = \"transition\";\nconst ANIMATION = \"animation\";\n\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function resolveTransitionProps(\n  rawProps: TransitionProps,\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    leaveFromClass = `${name}-leave-from`,\n    leaveActiveClass = `${name}-leave-active`,\n    leaveToClass = `${name}-leave-to`,\n    mode = \"default\",\n    onBeforeEnter,\n    onEnter,\n    onAfterEnter,\n    onBeforeLeave,\n    onLeave,\n    onAfterLeave,\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  const finishEnter = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, enterToClass);\n    removeTransitionClass(el, enterActiveClass);\n    done && done();\n    onAfterEnter && onAfterEnter(el);\n  };\n\n  const finishLeave = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, leaveToClass);\n    removeTransitionClass(el, leaveActiveClass);\n    done && done();\n    onAfterLeave && onAfterLeave(el);\n  };\n\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      onBeforeEnter && onBeforeEnter(el);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter(el) {\n      const resolve = () => finishEnter(el);\n      onEnter && onEnter(el, resolve);\n      nextFrame(() => {\n        removeTransitionClass(el, enterFromClass);\n        addTransitionClass(el, enterToClass);\n        if (!hasExplicitCallback(onEnter)) {\n          whenTransitionEnds(el, type, enterDuration, resolve);\n        }\n      });\n    },\n    leave(el, done) {\n      const resolve = () => finishLeave(el, done);\n      onBeforeLeave && onBeforeLeave(el);\n      addTransitionClass(el, leaveFromClass);\n      forceReflow();\n      addTransitionClass(el, leaveActiveClass);\n      nextFrame(() => {\n        removeTransitionClass(el, leaveFromClass);\n        addTransitionClass(el, leaveToClass);\n        if (!hasExplicitCallback(onLeave)) {\n          whenTransitionEnds(el, type, leaveDuration, resolve);\n        }\n      });\n      onLeave && onLeave(el, resolve);\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n\nfunction normalizeDuration(duration: TransitionProps[\"duration\"]): [number, number] | null {\n  if (duration == null) {\n    return null;\n  } else if (typeof duration === \"object\") {\n    return [NumberOf(duration.enter), NumberOf(duration.leave)];\n  } else {\n    const n = NumberOf(duration);\n    return [n, n];\n  }\n}\n\nfunction NumberOf(val: unknown): number {\n  const res = Number(val);\n  return isNaN(res) ? 0 : res;\n}\n\nexport function addTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n\nfunction hasExplicitCallback(hook: ((el: Element, done: () => void) => void) | undefined): boolean {\n  return hook ? hook.length > 1 : false;\n}\n\nexport function forceReflow(): void {\n  document.body.offsetHeight;\n}\n\ninterface CSSTransitionInfo {\n  type: typeof TRANSITION | typeof ANIMATION | null;\n  propCount: number;\n  timeout: number;\n  hasTransform: boolean;\n}\n\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"],\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n  const getStyleProperties = (key: keyof CSSStyleDeclaration) => (styles[key] || \"\") as string;\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION;\n      timeout = animationTimeout;\n      propCount = animationDurations.length;\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null;\n    propCount = type\n      ? type === TRANSITION\n        ? transitionDurations.length\n        : animationDurations.length\n      : 0;\n  }\n\n  const hasTransform =\n    type === TRANSITION && /\\b(transform|all)(,|$)/.test(getStyleProperties(\"transitionProperty\"));\n\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform,\n  };\n}\n\nfunction getTimeout(delays: string[], durations: string[]): number {\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays);\n  }\n  return Math.max(...durations.map((d, i) => toMs(d) + toMs(delays[i])));\n}\n\nfunction toMs(s: string): number {\n  return Number(s.slice(0, -1).replace(\",\", \".\")) * 1000;\n}\n\nlet endId = 0;\n\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void,\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout) as unknown as void;\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\";\n  let ended = 0;\n\n  const end = () => {\n    el.removeEventListener(endEvent, onEnd);\n    resolveIfNotStale();\n  };\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n\n// Transition component wrapper\nexport const Transition = {\n  name: \"Transition\",\n  props: {\n    name: String,\n    type: String,\n    css: { type: Boolean, default: true },\n    duration: [Number, Object],\n    enterFromClass: String,\n    enterActiveClass: String,\n    enterToClass: String,\n    leaveFromClass: String,\n    leaveActiveClass: String,\n    leaveToClass: String,\n    mode: String,\n    onBeforeEnter: Function,\n    onEnter: Function,\n    onAfterEnter: Function,\n    onBeforeLeave: Function,\n    onLeave: Function,\n    onAfterLeave: Function,\n  },\n  setup(props: TransitionProps, { slots }: { slots: any }) {\n    return () => {\n      const children = slots.default && slots.default();\n      if (!children || !children.length) {\n        return undefined;\n      }\n\n      const child = children[0];\n      if (child) {\n        const innerProps = resolveTransitionProps(props);\n        child.transition = innerProps;\n      }\n      return child;\n    };\n  },\n} as any;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\nexport { Transition } from \"./components/Transition\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/helpers/ssrInterpolate.ts",
    "content": "import { toDisplayString } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrInterpolate(value: unknown): string {\n  return escapeHtml(toDisplayString(value));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/helpers/ssrRenderAttrs.ts",
    "content": "import { isArray, isFunction, isOn, isString, normalizeClass, normalizeStyle } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrRenderAttrs(props: Record<string, unknown>, tag?: string): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (ssrIsIgnoredKey(key) || isOn(key) || (tag === \"textarea\" && key === \"value\")) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return key === \"key\" || key === \"ref\" || key === \"innerHTML\" || key === \"textContent\";\n}\n\nexport function ssrRenderDynamicAttr(key: string, value: unknown, tag?: string): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag)) ? key : propsToAttrMap[key] || key.toLowerCase();\n\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\" ? ` ${attrKey}` : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(`[server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`);\n    return \"\";\n  }\n}\n\nexport function ssrRenderAttr(key: string, value: unknown): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  return ` ${key}=\"${escapeHtml(value)}\"`;\n}\n\nfunction isRenderableAttrValue(value: unknown): boolean {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(styles: Record<string, string | number> | null): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n\nfunction hyphenate(str: string): string {\n  return str.replace(/\\B([A-Z])/g, \"-$1\").toLowerCase();\n}\n\n// Maps props to their corresponding HTML attribute names\nconst propsToAttrMap: Record<string, string> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n\n// Boolean attributes\nconst isBooleanAttr = (key: string): boolean => booleanAttrsSet.has(key);\n\nconst booleanAttrsSet = new Set(\n  (\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n    \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n    \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n    \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n    \"required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"\n  ).split(\",\"),\n);\n\n// Checks if the attribute name is safe for SSR\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nfunction isSSRSafeAttrName(name: string): boolean {\n  return !unsafeAttrCharRE.test(name);\n}\n\n// SVG tags\nconst SVG_TAGS = new Set(\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\".split(\n    \",\",\n  ),\n);\n\nfunction isSVGTag(tag: string): boolean {\n  return SVG_TAGS.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/helpers/ssrRenderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\n\nexport function ssrRenderList(\n  source: unknown,\n  renderItem: (value: unknown, key: string | number, index?: number) => void,\n): void {\n  if (isArray(source) || isString(source)) {\n    for (let i = 0, l = source.length; i < l; i++) {\n      renderItem(source[i], i);\n    }\n  } else if (typeof source === \"number\") {\n    for (let i = 0; i < source; i++) {\n      renderItem(i + 1, i);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      const arr = Array.from(source as Iterable<any>);\n      for (let i = 0, l = arr.length; i < l; i++) {\n        renderItem(arr[i], i);\n      }\n    } else {\n      const keys = Object.keys(source);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        renderItem((source as Record<string, unknown>)[key], key, i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/helpers/ssrUtils.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n\n// void elements\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nconst isVoidTagSet = new Set(VOID_TAGS.split(\",\"));\nexport function isVoidTag(tag: string): boolean {\n  return isVoidTagSet.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/index.ts",
    "content": "// public\nexport type { SSRContext } from \"./render\";\nexport { renderToString } from \"./renderToString\";\n\n// internal runtime helpers\nexport { ssrInterpolate } from \"./helpers/ssrInterpolate\";\nexport { ssrRenderList } from \"./helpers/ssrRenderList\";\nexport {\n  ssrRenderAttrs,\n  ssrRenderClass,\n  ssrRenderStyle,\n  ssrRenderAttr,\n  ssrRenderDynamicAttr,\n} from \"./helpers/ssrRenderAttrs\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/render.ts",
    "content": "import {\n  Comment,\n  type Component,\n  type ComponentInternalInstance,\n  type DirectiveBinding,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  type VNodeProps,\n  mergeProps,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  normalizeVNode,\n} from \"../runtime-core\";\nimport { ShapeFlags, isArray, isFunction, isPromise, isString } from \"../shared\";\nimport { ssrRenderAttrs } from \"./helpers/ssrRenderAttrs\";\nimport { escapeHtml, escapeHtmlComment, isVoidTag } from \"./helpers/ssrUtils\";\n\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\nexport type Props = Record<string, unknown>;\n\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  /**\n   * @internal\n   */\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  /**\n   * @internal\n   */\n  __watcherHandles?: (() => void)[];\n};\n\n// Each component has a buffer array.\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = (vnode.component = createComponentInstance(vnode, parentComponent, null as any));\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() => renderComponentSubTree(instance));\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    const root = (comp as Function)(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`);\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      }\n  }\n}\n\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const {\n      dir: { getSSRProps },\n    } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/server-renderer/renderToString.ts",
    "content": "import { type App, type VNode, createVNode, isVNode } from \"../runtime-core\";\nimport { isPromise, isString } from \"../shared\";\nimport { type SSRBuffer, type SSRContext, renderComponentVNode } from \"./render\";\n\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // raw vnode, wrap with app\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }, null, null),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // rendering an app\n  const app = input;\n  const vnode = createVNode(app._component, app._props, null);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n\nexport const isOn = (key: string) =>\n  key.charCodeAt(0) === 111 /* o */ &&\n  key.charCodeAt(1) === 110 /* n */ &&\n  (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return (\n    (isObject(val) || isFunction(val)) &&\n    isFunction((val as any).then) &&\n    isFunction((val as any).catch)\n  );\n};\n\nexport const toDisplayString = (val: unknown): string => {\n  return isString(val)\n    ? val\n    : val == null\n      ? \"\"\n      : isArray(val) ||\n          (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))\n        ? JSON.stringify(val, null, 2)\n        : String(val);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\nexport * from \"./shapeFlags\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  STATEFUL_COMPONENT = 1 << 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,\n  COMPONENT_KEPT_ALIVE = 1 << 9,\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport {\n  createApp,\n  h,\n  renderToString,\n  KeepAlive,\n  onActivated,\n  onDeactivated,\n  ref,\n  Transition,\n} from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"90_web_application_essentials/010_ssr\", () => {\n  it(\"should render to string\", async () => {\n    const app = createApp({\n      template: `<div>Hello SSR!</div>`,\n    });\n\n    const html = await renderToString(app);\n    // Single root element, but still wrapped in fragment comments by the template compiler\n    expect(html).toContain(\"<div>Hello SSR!</div>\");\n  });\n\n  it(\"should escape HTML in text content\", async () => {\n    const app = createApp({\n      template: `<div>{{ text }}</div>`,\n      setup() {\n        return { text: \"<script>alert('xss')</script>\" };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;\");\n  });\n\n  it(\"should render with class and style bindings\", async () => {\n    const app = createApp({\n      template: `<div :class=\"cls\" :style=\"styles\">content</div>`,\n      setup() {\n        return {\n          cls: { active: true, disabled: false },\n          styles: { color: \"red\", fontSize: \"14px\" },\n        };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain('class=\"active\"');\n    expect(html).toContain('style=\"color:red;font-size:14px;\"');\n  });\n\n  it(\"should render fragment\", async () => {\n    const app = createApp({\n      template: `<p>one</p><p>two</p>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<p>one</p><p>two</p>\");\n  });\n\n  it(\"should render nested components\", async () => {\n    const Child = {\n      props: [\"name\"],\n      template: `<span>{{ name }}</span>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<div><Child name=\"test\" /></div>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<span>\");\n    expect(html).toContain(\"</span>\");\n    expect(html).toContain(\"<div>\");\n  });\n\n  it(\"should render with h function\", async () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", { class: \"test\" }, \"Hello from h()\");\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toBe('<div class=\"test\">Hello from h()</div>');\n  });\n});\n\ndescribe(\"90_web_application_essentials/020_keep_alive\", () => {\n  it(\"should render KeepAlive wrapper\", () => {\n    const Child = {\n      name: \"Child\",\n      template: `<div>child content</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child content\");\n  });\n\n  it(\"should set COMPONENT_SHOULD_KEEP_ALIVE flag\", () => {\n    const Child = {\n      name: \"Child\",\n      render() {\n        return h(\"div\", null, \"child\");\n      },\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n  });\n\n  it(\"should render with activated/deactivated hooks registered\", () => {\n    let activated = false;\n    let deactivated = false;\n\n    const Child = {\n      name: \"Child\",\n      setup() {\n        onActivated(() => {\n          activated = true;\n        });\n        onDeactivated(() => {\n          deactivated = true;\n        });\n        return {};\n      },\n      template: `<div>child</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n    // Hooks are registered but not yet called\n    expect(activated).toBe(false);\n    expect(deactivated).toBe(false);\n  });\n});\n\ndescribe(\"90_web_application_essentials/030_transition\", () => {\n  it(\"should render Transition component with child\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => [h(\"div\", { class: \"content\" }, \"Hello Transition\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Hello Transition\");\n    expect(host.innerHTML).toContain(\"content\");\n  });\n\n  it(\"should apply transition hooks to child vnode\", () => {\n    const show = ref(true);\n\n    const app = createApp({\n      setup() {\n        return { show };\n      },\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => (show.value ? [h(\"div\", null, \"visible\")] : []),\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"visible\");\n  });\n\n  it(\"should support custom transition classes\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          {\n            enterFromClass: \"custom-enter-from\",\n            enterActiveClass: \"custom-enter-active\",\n            enterToClass: \"custom-enter-to\",\n            leaveFromClass: \"custom-leave-from\",\n            leaveActiveClass: \"custom-leave-active\",\n            leaveToClass: \"custom-leave-to\",\n          },\n          {\n            default: () => [h(\"div\", null, \"custom classes\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"custom classes\");\n  });\n\n  it(\"should return undefined when no children\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => [],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    // When no children, renders as comment node\n    expect(host.innerHTML).toBe(\"<!---->\");\n  });\n\n  it(\"should support mode prop\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\", mode: \"out-in\" },\n          {\n            default: () => [h(\"div\", null, \"out-in mode\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"out-in mode\");\n  });\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/030_transition/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\n\nconst count = ref(0)\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Static Hoisting Example</h2>\n\n    <!-- Static content (hoisted) -->\n    <div class=\"static-content\">\n      <h3>This is static content</h3>\n      <p>Static elements are hoisted out of render function for performance</p>\n      <ul>\n        <li>Static item 1</li>\n        <li>Static item 2</li>\n        <li>Static item 3</li>\n      </ul>\n    </div>\n\n    <!-- Dynamic content -->\n    <div class=\"dynamic-content\">\n      <p>Count: {{ count }}</p>\n      <button @click=\"count++\">Increment</button>\n    </div>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n.static-content {\n  background-color: #f0f0f0;\n  padding: 16px;\n  margin-bottom: 16px;\n  border-radius: 4px;\n}\n.dynamic-content {\n  background-color: #e0f0e0;\n  padding: 16px;\n  border-radius: 4px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,\n  CAN_SKIP_PATCH,\n  CAN_HOIST,\n  CAN_STRINGIFY,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  constType: ConstantTypes;\n  identifiers?: string[];\n  hoisted?: JSChildNode;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n  hoists: (JSChildNode | null)[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    hoists: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n  constType: ConstantTypes = isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    constType,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  ConstantTypes,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  // Generate hoisted variables before render function\n  genHoists(ast.hoists, context, option);\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context);\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genHoists(\n  hoists: (JSChildNode | null)[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  if (!hoists.length) {\n    return;\n  }\n  const { push, newline } = context;\n  newline();\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context, option);\n      newline();\n    }\n  }\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  hoistStatic?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  ConstantTypes,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type JSChildNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  root: RootNode;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  hoists: (JSChildNode | null)[];\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  hoist(exp: string | JSChildNode): SimpleExpressionNode;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    root,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    hoists: [],\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    hoist(exp) {\n      if (isString(exp)) exp = createSimpleExpression(exp);\n      context.hoists.push(exp);\n      const identifier = createSimpleExpression(\n        `_hoisted_${context.hoists.length}`,\n        false,\n        exp.loc,\n        ConstantTypes.CAN_HOIST,\n      );\n      identifier.hoisted = exp;\n      return identifier;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n  root.hoists = context.hoists;\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nfunction hoistStatic(root: RootNode, context: TransformContext) {\n  walk(\n    root,\n    context,\n    // Root node is already transformed\n    isSingleElementRoot(root, root.children[0]),\n  );\n}\n\nfunction isSingleElementRoot(root: RootNode, child: TemplateChildNode): child is ElementNode {\n  return root.children.length === 1 && child.type === NodeTypes.ELEMENT;\n}\n\nfunction walk(node: ParentNode, context: TransformContext, doNotHoistNode: boolean = false) {\n  const { children } = node;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    // only plain elements are eligible for hoisting\n    if (child.type === NodeTypes.ELEMENT && child.tagType === ElementTypes.ELEMENT) {\n      const constantType = doNotHoistNode\n        ? ConstantTypes.NOT_CONSTANT\n        : getConstantType(child, context);\n\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          (child.codegenNode as any).patchFlag = -1 /* HOISTED */;\n          child.codegenNode = context.hoist(child.codegenNode!);\n          continue;\n        }\n      }\n    }\n\n    // walk further\n    if (child.type === NodeTypes.ELEMENT) {\n      walk(child, context);\n    } else if (child.type === NodeTypes.FOR) {\n      // Do not hoist v-for single child\n      walk(child, context, child.children.length === 1);\n    } else if (child.type === NodeTypes.IF) {\n      for (let i = 0; i < child.branches.length; i++) {\n        walk(child.branches[i], context, child.branches[i].children.length === 1);\n      }\n    }\n  }\n}\n\nfunction getConstantType(node: TemplateChildNode, context: TransformContext): ConstantTypes {\n  const { constantCache } = context as any;\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      if (node.tagType !== ElementTypes.ELEMENT) {\n        return ConstantTypes.NOT_CONSTANT;\n      }\n      const cached = constantCache?.get(node);\n      if (cached !== undefined) {\n        return cached;\n      }\n      const codegenNode = node.codegenNode!;\n      if (codegenNode.type !== NodeTypes.VNODE_CALL) {\n        return ConstantTypes.NOT_CONSTANT;\n      }\n      // has dynamic props\n      const flag = getPatchFlag(codegenNode);\n      if (!flag) {\n        let returnType = ConstantTypes.CAN_STRINGIFY;\n\n        // check children\n        for (let i = 0; i < node.children.length; i++) {\n          const childType = getConstantType(node.children[i], context);\n          if (childType === ConstantTypes.NOT_CONSTANT) {\n            constantCache?.set(node, ConstantTypes.NOT_CONSTANT);\n            return ConstantTypes.NOT_CONSTANT;\n          }\n          if (childType < returnType) {\n            returnType = childType;\n          }\n        }\n\n        // check props\n        for (let i = 0; i < node.props.length; i++) {\n          const p = node.props[i];\n          if (p.type === NodeTypes.DIRECTIVE) {\n            constantCache?.set(node, ConstantTypes.NOT_CONSTANT);\n            return ConstantTypes.NOT_CONSTANT;\n          }\n        }\n\n        if (returnType > ConstantTypes.CAN_SKIP_PATCH) {\n          constantCache?.set(node, returnType);\n          return returnType;\n        } else {\n          constantCache?.set(node, ConstantTypes.CAN_SKIP_PATCH);\n          return ConstantTypes.CAN_SKIP_PATCH;\n        }\n      } else {\n        constantCache?.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n    case NodeTypes.TEXT:\n    case NodeTypes.COMMENT:\n      return ConstantTypes.CAN_STRINGIFY;\n    case NodeTypes.IF:\n    case NodeTypes.FOR:\n      return ConstantTypes.NOT_CONSTANT;\n    case NodeTypes.INTERPOLATION:\n      return ConstantTypes.NOT_CONSTANT;\n    default:\n      return ConstantTypes.NOT_CONSTANT;\n  }\n}\n\nfunction getPatchFlag(node: any): number | undefined {\n  const flag = node.patchFlag;\n  return flag ? parseInt(flag, 10) : undefined;\n}\n\nimport { ElementTypes } from \"./ast\";\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nexport * from \"./server-renderer\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n\n  // internal, for SSR\n  _component: Component;\n  _props: Record<string, any> | null;\n  _context: AppContext;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent, rootProps = null) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      _component: rootComponent,\n      _props: rootProps,\n      _context: context,\n\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, rootProps, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n\n// KeepAlive lifecycle hooks\nexport const onActivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.a || (target.a = []);\n    hooks.push(hook);\n  }\n};\n\nexport const onDeactivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.da || (target.da = []);\n    hooks.push(hook);\n  }\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  attrs: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  isDeactivated: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n\n  // KeepAlive lifecycle hooks\n  a: LifecycleHook; // activated\n  da: LifecycleHook; // deactivated\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    attrs: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    isDeactivated: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n\n    // KeepAlive lifecycle hooks\n    a: null,\n    da: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\n\nexport const getCurrentInstance = (): ComponentInternalInstance | null => {\n  return currentInstance;\n};\n\nexport const setCurrentInstance = (instance: ComponentInternalInstance | null) => {\n  const prev = currentInstance;\n  currentInstance = instance;\n  if (instance) {\n    instance.scope.on();\n  }\n  return prev;\n};\n\nexport const unsetCurrentInstance = (prev: ComponentInternalInstance | null = null) => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = prev;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    const prev = setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance(prev);\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  const prev = setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance(prev);\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/components/KeepAlive.ts",
    "content": "import { ShapeFlags, isArray, isString } from \"../../shared\";\nimport type { ComponentInternalInstance, Data } from \"../component\";\nimport { getCurrentInstance } from \"../component\";\nimport type { VNode } from \"../vnode\";\nimport { isSameVNodeType } from \"../vnode\";\nimport { queuePostFlushCb } from \"../scheduler\";\n\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n\ninterface KeepAliveRenderer {\n  p: (\n    n1: VNode | null,\n    n2: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  m: (vnode: VNode, container: any, anchor: any | null) => void;\n  um: (vnode: VNode) => void;\n  o: {\n    createElement: (type: string) => any;\n  };\n}\n\nconst KeepAliveImpl = {\n  name: `KeepAlive`,\n  __isKeepAlive: true,\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number],\n  },\n  setup(props: KeepAliveProps, { slots }: { slots: any }) {\n    const instance = getCurrentInstance()! as KeepAliveContext;\n    const cache: Map<any, VNode> = new Map();\n    const keys: Set<any> = new Set();\n    let current: VNode | null = null;\n\n    const parentComponent = instance.parent as ComponentInternalInstance;\n\n    // Create a hidden container for holding deactivated components\n    const storageContainer = instance.renderer.o.createElement(\"div\");\n\n    instance.activate = (vnode, container, anchor, _parentComponent) => {\n      const instance = vnode.component!;\n      move(vnode, container, anchor);\n      // in case props have changed\n      patch(instance.vnode, vnode, container, anchor, parentComponent);\n      queuePostFlushCb(() => {\n        instance.isDeactivated = false;\n        if (instance.a) {\n          instance.a.forEach((hook: () => void) => hook());\n        }\n      });\n    };\n\n    instance.deactivate = (vnode: VNode) => {\n      move(vnode, storageContainer, null);\n      queuePostFlushCb(() => {\n        const instance = vnode.component!;\n        if (instance.da) {\n          instance.da.forEach((hook: () => void) => hook());\n        }\n        instance.isDeactivated = true;\n      });\n    };\n\n    function move(vnode: VNode, container: any, anchor: any | null): void {\n      instance.renderer.m(vnode, container, anchor);\n    }\n\n    function patch(\n      n1: VNode | null,\n      n2: VNode,\n      container: any,\n      anchor: any | null,\n      parentComponent: ComponentInternalInstance | null,\n    ): void {\n      instance.renderer.p(n1, n2, container, anchor, parentComponent);\n    }\n\n    function unmount(vnode: VNode): void {\n      resetShapeFlag(vnode);\n      instance.renderer.um(vnode);\n    }\n\n    function pruneCacheEntry(key: any): void {\n      const cached = cache.get(key) as VNode;\n      if (!current || !isSameVNodeType(cached, current)) {\n        unmount(cached);\n      } else if (current) {\n        resetShapeFlag(current);\n      }\n      cache.delete(key);\n      keys.delete(key);\n    }\n\n    return (): VNode | undefined => {\n      if (!slots.default) {\n        return undefined;\n      }\n\n      const children = slots.default();\n      const rawVNode = children[0];\n      if (children.length > 1) {\n        current = null;\n        return children as unknown as VNode;\n      } else if (\n        !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n        !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n      ) {\n        current = null;\n        return rawVNode;\n      }\n\n      let vnode = rawVNode;\n      const comp = vnode.type as any;\n      const name = getComponentName(comp);\n      const { include, exclude, max } = props;\n\n      if (\n        (include && (!name || !matches(include, name))) ||\n        (exclude && name && matches(exclude, name))\n      ) {\n        current = vnode;\n        return rawVNode;\n      }\n\n      const key = vnode.key == null ? comp : vnode.key;\n      const cachedVNode = cache.get(key);\n\n      if (cachedVNode) {\n        vnode.el = cachedVNode.el;\n        vnode.component = cachedVNode.component;\n        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n        keys.delete(key);\n        keys.add(key);\n      } else {\n        keys.add(key);\n        if (max && keys.size > parseInt(max as string, 10)) {\n          pruneCacheEntry(keys.values().next().value);\n        }\n      }\n\n      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n      current = vnode;\n      return vnode;\n    };\n  },\n};\n\nexport const KeepAlive = KeepAliveImpl as any as {\n  __isKeepAlive: true;\n  new (): {\n    $props: KeepAliveProps;\n  };\n};\n\nexport function isKeepAlive(vnode: VNode): boolean {\n  return (vnode.type as any).__isKeepAlive === true;\n}\n\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n\nfunction getComponentName(comp: { name?: string; __name?: string }): string | undefined {\n  return comp.name || comp.__name;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport {\n  registerRuntimeCompiler,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  getCurrentInstance,\n  type InternalRenderFunction,\n  type ComponentInternalInstance,\n  type Component,\n} from \"./component\";\n\nexport { KeepAlive } from \"./components/KeepAlive\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  onActivated,\n  onDeactivated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport {\n  createVNode,\n  createCommentVNode,\n  normalizeVNode,\n  mergeProps,\n  isVNode,\n  Fragment,\n  Text,\n  Comment,\n  type VNode,\n  type VNodeProps,\n  type VNodeArrayChildren,\n  type DirectiveBinding,\n} from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { type KeepAliveContext, isKeepAlive } from \"./components/KeepAlive\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode, parentComponent?: ComponentInternalInstance | null) => {\n    const { children, shapeFlag } = vnode;\n\n    // KeepAlive: deactivate instead of unmount\n    if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n      (parentComponent as KeepAliveContext).deactivate(vnode);\n      return;\n    }\n\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n        // Restore from cache\n        (parentComponent as KeepAliveContext).activate(n2, container, anchor, parentComponent);\n      } else {\n        mountComponent(n2, container, anchor, parentComponent);\n      }\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n\n    // Inject renderer internals for KeepAlive\n    if (isKeepAlive(initialVNode)) {\n      (instance as KeepAliveContext).renderer = {\n        p: patch,\n        m: move,\n        um: unmount,\n        o: {\n          createElement: hostCreateElement,\n        },\n      };\n    }\n\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface TransitionHooks<HostElement = any> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nexport interface VNode<HostNode = any> {\n  __v_isVNode: true;\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // application root node only\n  appContext: AppContext | null;\n\n  // directives\n  dirs?: DirectiveBinding[] | null;\n\n  // transition hooks\n  transition?: TransitionHooks<HostNode> | null;\n}\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentInternalInstance | null;\n  value: V;\n  oldValue: V | null;\n  arg?: string;\n  modifiers: Record<string, boolean>;\n  dir: ObjectDirective<any, V>;\n}\n\nexport interface ObjectDirective<T = any, V = any> {\n  created?: DirectiveHook<T, null, V>;\n  beforeMount?: DirectiveHook<T, null, V>;\n  mounted?: DirectiveHook<T, null, V>;\n  beforeUpdate?: DirectiveHook<T, VNode<T>, V>;\n  updated?: DirectiveHook<T, VNode<T>, V>;\n  beforeUnmount?: DirectiveHook<T, null, V>;\n  unmounted?: DirectiveHook<T, null, V>;\n  getSSRProps?: (binding: DirectiveBinding<V>, vnode: VNode) => Record<string, unknown> | undefined;\n}\n\nexport type DirectiveHook<T = any, Prev = VNode<T> | null, V = any> = (\n  el: T,\n  binding: DirectiveBinding<V>,\n  vnode: VNode<T>,\n  prevVNode: Prev,\n) => void;\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(type: VNodeTypes, props: VNodeProps | null, children: any): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    __v_isVNode: true,\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    appContext: null,\n    dirs: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (child == null || typeof child === \"boolean\") {\n    return createVNode(Comment, null, \"\");\n  } else if (isArray(child)) {\n    return createVNode(Fragment, null, child.slice());\n  } else if (typeof child === \"object\") {\n    return cloneIfMounted(child as VNode);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nfunction cloneIfMounted(child: VNode): VNode {\n  return child.el === null ? child : ({ ...child, __v_isVNode: true } as VNode);\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function isVNode(value: any): value is VNode {\n  return value ? value.__v_isVNode === true : false;\n}\n\nexport function cloneVNode(vnode: VNode, extraProps?: VNodeProps | null): VNode {\n  const { props, children } = vnode;\n  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;\n  const cloned: VNode = {\n    __v_isVNode: true,\n    type: vnode.type,\n    props: mergedProps,\n    key: mergedProps?.key ?? vnode.key,\n    ref: mergedProps?.ref ?? vnode.ref,\n    children: children,\n    component: vnode.component,\n    el: vnode.el,\n    anchor: vnode.anchor,\n    shapeFlag: vnode.shapeFlag,\n    appContext: vnode.appContext,\n    dirs: vnode.dirs,\n  };\n  return cloned;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/components/Transition.ts",
    "content": "import type { VNode } from \"../../runtime-core\";\n\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n}\n\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nconst TRANSITION = \"transition\";\nconst ANIMATION = \"animation\";\n\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function resolveTransitionProps(\n  rawProps: TransitionProps,\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    leaveFromClass = `${name}-leave-from`,\n    leaveActiveClass = `${name}-leave-active`,\n    leaveToClass = `${name}-leave-to`,\n    mode = \"default\",\n    onBeforeEnter,\n    onEnter,\n    onAfterEnter,\n    onBeforeLeave,\n    onLeave,\n    onAfterLeave,\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  const finishEnter = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, enterToClass);\n    removeTransitionClass(el, enterActiveClass);\n    done && done();\n    onAfterEnter && onAfterEnter(el);\n  };\n\n  const finishLeave = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, leaveToClass);\n    removeTransitionClass(el, leaveActiveClass);\n    done && done();\n    onAfterLeave && onAfterLeave(el);\n  };\n\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      onBeforeEnter && onBeforeEnter(el);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter(el) {\n      const resolve = () => finishEnter(el);\n      onEnter && onEnter(el, resolve);\n      nextFrame(() => {\n        removeTransitionClass(el, enterFromClass);\n        addTransitionClass(el, enterToClass);\n        if (!hasExplicitCallback(onEnter)) {\n          whenTransitionEnds(el, type, enterDuration, resolve);\n        }\n      });\n    },\n    leave(el, done) {\n      const resolve = () => finishLeave(el, done);\n      onBeforeLeave && onBeforeLeave(el);\n      addTransitionClass(el, leaveFromClass);\n      forceReflow();\n      addTransitionClass(el, leaveActiveClass);\n      nextFrame(() => {\n        removeTransitionClass(el, leaveFromClass);\n        addTransitionClass(el, leaveToClass);\n        if (!hasExplicitCallback(onLeave)) {\n          whenTransitionEnds(el, type, leaveDuration, resolve);\n        }\n      });\n      onLeave && onLeave(el, resolve);\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n\nfunction normalizeDuration(duration: TransitionProps[\"duration\"]): [number, number] | null {\n  if (duration == null) {\n    return null;\n  } else if (typeof duration === \"object\") {\n    return [NumberOf(duration.enter), NumberOf(duration.leave)];\n  } else {\n    const n = NumberOf(duration);\n    return [n, n];\n  }\n}\n\nfunction NumberOf(val: unknown): number {\n  const res = Number(val);\n  return isNaN(res) ? 0 : res;\n}\n\nexport function addTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n\nfunction hasExplicitCallback(hook: ((el: Element, done: () => void) => void) | undefined): boolean {\n  return hook ? hook.length > 1 : false;\n}\n\nexport function forceReflow(): void {\n  document.body.offsetHeight;\n}\n\ninterface CSSTransitionInfo {\n  type: typeof TRANSITION | typeof ANIMATION | null;\n  propCount: number;\n  timeout: number;\n  hasTransform: boolean;\n}\n\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"],\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n  const getStyleProperties = (key: keyof CSSStyleDeclaration) => (styles[key] || \"\") as string;\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION;\n      timeout = animationTimeout;\n      propCount = animationDurations.length;\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null;\n    propCount = type\n      ? type === TRANSITION\n        ? transitionDurations.length\n        : animationDurations.length\n      : 0;\n  }\n\n  const hasTransform =\n    type === TRANSITION && /\\b(transform|all)(,|$)/.test(getStyleProperties(\"transitionProperty\"));\n\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform,\n  };\n}\n\nfunction getTimeout(delays: string[], durations: string[]): number {\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays);\n  }\n  return Math.max(...durations.map((d, i) => toMs(d) + toMs(delays[i])));\n}\n\nfunction toMs(s: string): number {\n  return Number(s.slice(0, -1).replace(\",\", \".\")) * 1000;\n}\n\nlet endId = 0;\n\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void,\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout) as unknown as void;\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\";\n  let ended = 0;\n\n  const end = () => {\n    el.removeEventListener(endEvent, onEnd);\n    resolveIfNotStale();\n  };\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n\n// Transition component wrapper\nexport const Transition = {\n  name: \"Transition\",\n  props: {\n    name: String,\n    type: String,\n    css: { type: Boolean, default: true },\n    duration: [Number, Object],\n    enterFromClass: String,\n    enterActiveClass: String,\n    enterToClass: String,\n    leaveFromClass: String,\n    leaveActiveClass: String,\n    leaveToClass: String,\n    mode: String,\n    onBeforeEnter: Function,\n    onEnter: Function,\n    onAfterEnter: Function,\n    onBeforeLeave: Function,\n    onLeave: Function,\n    onAfterLeave: Function,\n  },\n  setup(props: TransitionProps, { slots }: { slots: any }) {\n    return () => {\n      const children = slots.default && slots.default();\n      if (!children || !children.length) {\n        return undefined;\n      }\n\n      const child = children[0];\n      if (child) {\n        const innerProps = resolveTransitionProps(props);\n        child.transition = innerProps;\n      }\n      return child;\n    };\n  },\n} as any;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\nexport { Transition } from \"./components/Transition\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/helpers/ssrInterpolate.ts",
    "content": "import { toDisplayString } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrInterpolate(value: unknown): string {\n  return escapeHtml(toDisplayString(value));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/helpers/ssrRenderAttrs.ts",
    "content": "import { isArray, isFunction, isOn, isString, normalizeClass, normalizeStyle } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrRenderAttrs(props: Record<string, unknown>, tag?: string): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (ssrIsIgnoredKey(key) || isOn(key) || (tag === \"textarea\" && key === \"value\")) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return key === \"key\" || key === \"ref\" || key === \"innerHTML\" || key === \"textContent\";\n}\n\nexport function ssrRenderDynamicAttr(key: string, value: unknown, tag?: string): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag)) ? key : propsToAttrMap[key] || key.toLowerCase();\n\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\" ? ` ${attrKey}` : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(`[server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`);\n    return \"\";\n  }\n}\n\nexport function ssrRenderAttr(key: string, value: unknown): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  return ` ${key}=\"${escapeHtml(value)}\"`;\n}\n\nfunction isRenderableAttrValue(value: unknown): boolean {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(styles: Record<string, string | number> | null): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n\nfunction hyphenate(str: string): string {\n  return str.replace(/\\B([A-Z])/g, \"-$1\").toLowerCase();\n}\n\n// Maps props to their corresponding HTML attribute names\nconst propsToAttrMap: Record<string, string> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n\n// Boolean attributes\nconst isBooleanAttr = (key: string): boolean => booleanAttrsSet.has(key);\n\nconst booleanAttrsSet = new Set(\n  (\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n    \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n    \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n    \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n    \"required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"\n  ).split(\",\"),\n);\n\n// Checks if the attribute name is safe for SSR\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nfunction isSSRSafeAttrName(name: string): boolean {\n  return !unsafeAttrCharRE.test(name);\n}\n\n// SVG tags\nconst SVG_TAGS = new Set(\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\".split(\n    \",\",\n  ),\n);\n\nfunction isSVGTag(tag: string): boolean {\n  return SVG_TAGS.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/helpers/ssrRenderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\n\nexport function ssrRenderList(\n  source: unknown,\n  renderItem: (value: unknown, key: string | number, index?: number) => void,\n): void {\n  if (isArray(source) || isString(source)) {\n    for (let i = 0, l = source.length; i < l; i++) {\n      renderItem(source[i], i);\n    }\n  } else if (typeof source === \"number\") {\n    for (let i = 0; i < source; i++) {\n      renderItem(i + 1, i);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      const arr = Array.from(source as Iterable<any>);\n      for (let i = 0, l = arr.length; i < l; i++) {\n        renderItem(arr[i], i);\n      }\n    } else {\n      const keys = Object.keys(source);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        renderItem((source as Record<string, unknown>)[key], key, i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/helpers/ssrUtils.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n\n// void elements\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nconst isVoidTagSet = new Set(VOID_TAGS.split(\",\"));\nexport function isVoidTag(tag: string): boolean {\n  return isVoidTagSet.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/index.ts",
    "content": "// public\nexport type { SSRContext } from \"./render\";\nexport { renderToString } from \"./renderToString\";\n\n// internal runtime helpers\nexport { ssrInterpolate } from \"./helpers/ssrInterpolate\";\nexport { ssrRenderList } from \"./helpers/ssrRenderList\";\nexport {\n  ssrRenderAttrs,\n  ssrRenderClass,\n  ssrRenderStyle,\n  ssrRenderAttr,\n  ssrRenderDynamicAttr,\n} from \"./helpers/ssrRenderAttrs\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/render.ts",
    "content": "import {\n  Comment,\n  type Component,\n  type ComponentInternalInstance,\n  type DirectiveBinding,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  type VNodeProps,\n  mergeProps,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  normalizeVNode,\n} from \"../runtime-core\";\nimport { ShapeFlags, isArray, isFunction, isPromise, isString } from \"../shared\";\nimport { ssrRenderAttrs } from \"./helpers/ssrRenderAttrs\";\nimport { escapeHtml, escapeHtmlComment, isVoidTag } from \"./helpers/ssrUtils\";\n\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\nexport type Props = Record<string, unknown>;\n\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  /**\n   * @internal\n   */\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  /**\n   * @internal\n   */\n  __watcherHandles?: (() => void)[];\n};\n\n// Each component has a buffer array.\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = (vnode.component = createComponentInstance(vnode, parentComponent, null as any));\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() => renderComponentSubTree(instance));\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    const root = (comp as Function)(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`);\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      }\n  }\n}\n\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const {\n      dir: { getSSRProps },\n    } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/server-renderer/renderToString.ts",
    "content": "import { type App, type VNode, createVNode, isVNode } from \"../runtime-core\";\nimport { isPromise, isString } from \"../shared\";\nimport { type SSRBuffer, type SSRContext, renderComponentVNode } from \"./render\";\n\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // raw vnode, wrap with app\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }, null, null),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // rendering an app\n  const app = input;\n  const vnode = createVNode(app._component, app._props, null);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n\nexport const isOn = (key: string) =>\n  key.charCodeAt(0) === 111 /* o */ &&\n  key.charCodeAt(1) === 110 /* n */ &&\n  (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return (\n    (isObject(val) || isFunction(val)) &&\n    isFunction((val as any).then) &&\n    isFunction((val as any).catch)\n  );\n};\n\nexport const toDisplayString = (val: unknown): string => {\n  return isString(val)\n    ? val\n    : val == null\n      ? \"\"\n      : isArray(val) ||\n          (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))\n        ? JSON.stringify(val, null, 2)\n        : String(val);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\nexport * from \"./shapeFlags\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  STATEFUL_COMPONENT = 1 << 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,\n  COMPONENT_KEPT_ALIVE = 1 << 9,\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport {\n  createApp,\n  h,\n  renderToString,\n  KeepAlive,\n  onActivated,\n  onDeactivated,\n  ref,\n  Transition,\n} from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"90_web_application_essentials/010_ssr\", () => {\n  it(\"should render to string\", async () => {\n    const app = createApp({\n      template: `<div>Hello SSR!</div>`,\n    });\n\n    const html = await renderToString(app);\n    // Single root element, but still wrapped in fragment comments by the template compiler\n    expect(html).toContain(\"<div>Hello SSR!</div>\");\n  });\n\n  it(\"should escape HTML in text content\", async () => {\n    const app = createApp({\n      template: `<div>{{ text }}</div>`,\n      setup() {\n        return { text: \"<script>alert('xss')</script>\" };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;\");\n  });\n\n  it(\"should render with class and style bindings\", async () => {\n    const app = createApp({\n      template: `<div :class=\"cls\" :style=\"styles\">content</div>`,\n      setup() {\n        return {\n          cls: { active: true, disabled: false },\n          styles: { color: \"red\", fontSize: \"14px\" },\n        };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain('class=\"active\"');\n    expect(html).toContain('style=\"color:red;font-size:14px;\"');\n  });\n\n  it(\"should render fragment\", async () => {\n    const app = createApp({\n      template: `<p>one</p><p>two</p>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<p>one</p><p>two</p>\");\n  });\n\n  it(\"should render nested components\", async () => {\n    const Child = {\n      props: [\"name\"],\n      template: `<span>{{ name }}</span>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<div><Child name=\"test\" /></div>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<span>\");\n    expect(html).toContain(\"</span>\");\n    expect(html).toContain(\"<div>\");\n  });\n\n  it(\"should render with h function\", async () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", { class: \"test\" }, \"Hello from h()\");\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toBe('<div class=\"test\">Hello from h()</div>');\n  });\n});\n\ndescribe(\"90_web_application_essentials/020_keep_alive\", () => {\n  it(\"should render KeepAlive wrapper\", () => {\n    const Child = {\n      name: \"Child\",\n      template: `<div>child content</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child content\");\n  });\n\n  it(\"should set COMPONENT_SHOULD_KEEP_ALIVE flag\", () => {\n    const Child = {\n      name: \"Child\",\n      render() {\n        return h(\"div\", null, \"child\");\n      },\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n  });\n\n  it(\"should render with activated/deactivated hooks registered\", () => {\n    let activated = false;\n    let deactivated = false;\n\n    const Child = {\n      name: \"Child\",\n      setup() {\n        onActivated(() => {\n          activated = true;\n        });\n        onDeactivated(() => {\n          deactivated = true;\n        });\n        return {};\n      },\n      template: `<div>child</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n    // Hooks are registered but not yet called\n    expect(activated).toBe(false);\n    expect(deactivated).toBe(false);\n  });\n});\n\ndescribe(\"90_web_application_essentials/030_transition\", () => {\n  it(\"should render Transition component with child\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => [h(\"div\", { class: \"content\" }, \"Hello Transition\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Hello Transition\");\n    expect(host.innerHTML).toContain(\"content\");\n  });\n\n  it(\"should apply transition hooks to child vnode\", () => {\n    const show = ref(true);\n\n    const app = createApp({\n      setup() {\n        return { show };\n      },\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => (show.value ? [h(\"div\", null, \"visible\")] : []),\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"visible\");\n  });\n\n  it(\"should support custom transition classes\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          {\n            enterFromClass: \"custom-enter-from\",\n            enterActiveClass: \"custom-enter-active\",\n            enterToClass: \"custom-enter-to\",\n            leaveFromClass: \"custom-leave-from\",\n            leaveActiveClass: \"custom-leave-active\",\n            leaveToClass: \"custom-leave-to\",\n          },\n          {\n            default: () => [h(\"div\", null, \"custom classes\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"custom classes\");\n  });\n\n  it(\"should return undefined when no children\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => [],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    // When no children, renders as comment node\n    expect(host.innerHTML).toBe(\"<!---->\");\n  });\n\n  it(\"should support mode prop\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\", mode: \"out-in\" },\n          {\n            default: () => [h(\"div\", null, \"out-in mode\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"out-in mode\");\n  });\n});\n\ndescribe(\"90_web_application_essentials/040_static_hoisting\", () => {\n  it(\"should render static elements correctly\", () => {\n    const app = createApp({\n      template: `<div><span>static</span><p>{{ msg }}</p></div>`,\n      setup() {\n        return { msg: \"dynamic\" };\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"static\");\n    expect(host.innerHTML).toContain(\"dynamic\");\n  });\n\n  it(\"should render multiple static children\", () => {\n    const app = createApp({\n      template: `<div><span>one</span><span>two</span><span>three</span></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"one\");\n    expect(host.innerHTML).toContain(\"two\");\n    expect(host.innerHTML).toContain(\"three\");\n  });\n\n  it(\"should render mixed static and dynamic content\", () => {\n    const count = ref(0);\n    const app = createApp({\n      setup() {\n        return { count };\n      },\n      template: `<div><header>Static Header</header><main>Count: {{ count }}</main><footer>Static Footer</footer></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Static Header\");\n    expect(host.innerHTML).toContain(\"Count: 0\");\n    expect(host.innerHTML).toContain(\"Static Footer\");\n  });\n\n  it(\"should preserve static attributes\", () => {\n    const app = createApp({\n      template: `<div><span class=\"static-class\" id=\"static-id\">content</span></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain('class=\"static-class\"');\n    expect(host.innerHTML).toContain('id=\"static-id\"');\n  });\n\n  it(\"should handle deeply nested static elements\", () => {\n    const app = createApp({\n      template: `<div><section><article><p>Deep static content</p></article></section></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Deep static content\");\n    expect(host.innerHTML).toContain(\"<section>\");\n    expect(host.innerHTML).toContain(\"<article>\");\n  });\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/040_static_hoisting/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\nconst color = ref('red')\nconst count = ref(0)\n</script>\n\n<template>\n  <div class=\"container\">\n    <h2>Patch Flags Example</h2>\n\n    <!-- TEXT flag: only text content is dynamic -->\n    <p>{{ message }}</p>\n\n    <!-- CLASS flag: only class binding is dynamic -->\n    <div :class=\"{ active: count > 0 }\">Class binding (count > 0)</div>\n\n    <!-- STYLE flag: only style binding is dynamic -->\n    <span :style=\"{ color: color }\">Styled text ({{ color }})</span>\n\n    <!-- PROPS flag: props are dynamic -->\n    <input :value=\"message\" @input=\"message = $event.target.value\" />\n\n    <div class=\"controls\">\n      <button @click=\"count++\">Increment ({{ count }})</button>\n      <button @click=\"color = color === 'red' ? 'blue' : 'red'\">Toggle color</button>\n    </div>\n  </div>\n</template>\n\n<style>\n.container {\n  padding: 16px;\n}\n.active {\n  background-color: #42b883;\n  color: white;\n  padding: 4px 8px;\n}\n.controls {\n  margin-top: 16px;\n}\nbutton {\n  margin-right: 8px;\n}\n</style>\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport chibivue from \"../../packages/@extensions/vite-plugin-chibivue\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [chibivue()],\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/package.json",
    "content": "{\n  \"name\": \"01_project_setup\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"keywords\": [],\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"magic-string\": \"^0.30.21\"\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/@extensions/vite-plugin-chibivue/index.ts",
    "content": "import fs from \"node:fs\";\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { parse, rewriteDefault } from \"../../compiler-sfc\";\nimport { compile } from \"../../compiler-dom\";\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n\n  return {\n    name: \"vite:chibivue\",\n    resolveId(id) {\n      if (id.match(/\\.vue\\.css$/)) return id;\n    },\n\n    load(id) {\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, \"\");\n        const content = fs.readFileSync(filename, \"utf-8\");\n        const { descriptor } = parse(content, { filename });\n        const styles = descriptor.styles.map((it) => it.content).join(\"\\n\");\n        return { code: styles };\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return;\n\n      const outputs = [];\n      outputs.push(\"import * as ChibiVue from 'chibivue'\");\n      outputs.push(`import '${id}.css'`);\n\n      const { descriptor } = parse(code, { filename: id });\n\n      const SFC_MAIN = \"_sfc_main\";\n      const scriptCode = rewriteDefault(descriptor.script?.content ?? \"\", SFC_MAIN);\n      outputs.push(scriptCode);\n\n      const templateCode = compile(descriptor.template?.content ?? \"\", {\n        isBrowser: false,\n      });\n      outputs.push(templateCode);\n\n      outputs.push(\"\\n\");\n      outputs.push(`export default { ...${SFC_MAIN}, render }`);\n\n      return { code: outputs.join(\"\\n\") };\n    },\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/ast.ts",
    "content": "import { isString } from \"../shared\";\nimport { CREATE_VNODE, type FRAGMENT, type RENDER_LIST, type RENDER_SLOT } from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  FOR,\n  IF,\n  IF_BRANCH,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_FUNCTION_EXPRESSION,\n  JS_ARRAY_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT,\n}\n\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,\n  CAN_SKIP_PATCH,\n  CAN_HOIST,\n  CAN_STRINGIFY,\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  constType: ConstantTypes;\n  identifiers?: string[];\n  hoisted?: JSChildNode;\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[];\n  identifiers?: string[];\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | undefined;\n  patchFlag: string | undefined;\n  dynamicProps: string | SimpleExpressionNode | undefined;\n  isBlock: boolean;\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\n\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  newline: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall;\n  helpers: Set<symbol>;\n  components: string[];\n  hoists: (JSChildNode | null)[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | SlotOutletNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  isSelfClosing: boolean;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SlotOutletNode extends BaseElementNode {\n  tagType: ElementTypes.SLOT;\n  codegenNode: RenderSlotCall | undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | ForNode\n  | IfNode\n  | IfBranchNode;\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\nexport interface RenderSlotCall extends CallExpression {\n  callee: typeof RENDER_SLOT;\n  // $slots, name\n  arguments: [string, string | ExpressionNode];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    helpers: new Set(),\n    components: [],\n    hoists: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  patchFlag?: string,\n  dynamicProps?: string | SimpleExpressionNode,\n  isBlock: boolean = false,\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(CREATE_VNODE);\n  }\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    patchFlag,\n    dynamicProps,\n    isBlock,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n  constType: ConstantTypes = isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    constType,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\ntype InferCodegenNodeType<T> = T extends typeof RENDER_SLOT ? RenderSlotCall : CallExpression;\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): InferCodegenNodeType<T> {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as InferCodegenNodeType<T>;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    loc,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/babelUtils.ts",
    "content": "import type { Function, Identifier, Node } from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function isReferencedIdentifier(id: Identifier, parent: Node | null, parentStack: Node[]) {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"../shared\";\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  ConstantTypes,\n  type ExpressionNode,\n  type FunctionExpression,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CompilerOptions } from \"./options\";\nimport { CREATE_COMMENT, CREATE_VNODE, RESOLVE_COMPONENT, helperNameMap } from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./utils\";\n\nconst CONSTANT = { ctxIdent: \"_ctx\" };\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\ntype CodegenNode = TemplateChildNode | JSChildNode;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  indentLevel: number;\n  line: 1;\n  column: 1;\n  offset: 0;\n  runtimeGlobalName: string;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: \"\",\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: \"ChibiVue\",\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  const context = createCodegenContext(ast);\n\n  const { push, newline } = context;\n\n  const args = [CONSTANT.ctxIdent];\n  const signature = args.join(\", \");\n\n  // Generate hoisted variables before render function\n  genHoists(ast.hoists, context, option);\n\n  if (option.isBrowser) {\n    push(\"return \");\n  }\n  push(`function render(${signature}) { `);\n\n  if (option.isBrowser) {\n    context.indent();\n    push(`with (_ctx) {`);\n  }\n\n  context.indent();\n  genFunctionPreamble(ast, context);\n\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    newline();\n    newline();\n  }\n\n  push(`return `);\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context, option);\n  } else {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  if (option.isBrowser) {\n    context.deindent();\n    push(` }`);\n  }\n\n  return context.code;\n};\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context;\n  const helpers = Array.from(ast.helpers);\n  push(`const { ${helpers.map(aliasHelper).join(\", \")} } = ${runtimeGlobalName}\\n`);\n  newline();\n}\n\nfunction genHoists(\n  hoists: (JSChildNode | null)[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  if (!hoists.length) {\n    return;\n  }\n  const { push, newline } = context;\n  newline();\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context, option);\n      newline();\n    }\n  }\n  newline();\n}\n\nfunction genAssets(\n  assets: string[],\n  type: \"component\" /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === \"component\") {\n    const resolver = helper(RESOLVE_COMPONENT);\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i];\n\n      push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`);\n      if (i < assets.length - 1) {\n        newline();\n      }\n    }\n  }\n}\n\nconst genNode = (node: CodegenNode | string, context: CodegenContext, option: CompilerOptions) => {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.FOR:\n    case NodeTypes.IF:\n      genNode(node.codegenNode!, context, option);\n      break;\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context, option);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context, option);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context, option);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context, option);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context, option);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context, option);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context, option);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default:\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n  }\n};\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNode(node.content, context, option);\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context, option);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(\n  node: ExpressionNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context, option);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const { tag, props, children, patchFlag, dynamicProps } = node;\n\n  push(helper(CREATE_VNODE) + `(`, node);\n  genNodeList(genNullableArgs([tag, props, children, patchFlag, dynamicProps]), context, option);\n  push(`)`);\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\nfunction genCallExpression(node: CallExpression, context: CodegenContext, option: CompilerOptions) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context, option);\n  push(`)`);\n}\n\nfunction genObjectExpression(\n  node: ObjectExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context, option);\n    push(`: `);\n    // value\n    genNode(value, context, option);\n    if (i < properties.length - 1) {\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(\n  node: ArrayExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  genNodeListAsArray(node.elements as CodegenNode[], context, option);\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context, option);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context, option);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context, option);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context, option);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  option: CompilerOptions,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context, option);\n    } else {\n      genNode(node, context, option);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context;\n  const { params, returns, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context, option);\n  } else if (params) {\n    genNode(params, context, option);\n  }\n  push(`) => `);\n  if (newline) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option);\n    } else {\n      genNode(returns, context, option);\n    }\n  }\n  if (newline) {\n    deindent();\n    push(`}`);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/compile.ts",
    "content": "import { generate } from \"./codegen\";\nimport type { CompilerOptions } from \"./options\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformSlotOutlet } from \"./transforms/transformSlotOutlet\";\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformFor } from \"./transforms/vFor\";\nimport { transformIf } from \"./transforms/vIf\";\nimport { transformOn } from \"./transforms/vOn\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformSlotOutlet, transformElement],\n    { bind: transformBind, on: transformOn },\n  ];\n}\n\nexport function baseCompile(template: string, option: CompilerOptions) {\n  const ast = baseParse(template.trim());\n\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n\n  transform(ast, {\n    ...option,\n    nodeTransforms: [...nodeTransforms],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...option.directiveTransforms,\n    },\n  });\n\n  const code = generate(ast, option);\n  return code;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/index.ts",
    "content": "export * from \"./codegen\";\nexport * from \"./compile\";\nexport * from \"./parse\";\nexport * from \"./ast\";\nexport * from \"./options\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/options.ts",
    "content": "import type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport type CompilerOptions = TransformOptions;\n\nexport interface TransformOptions {\n  isBrowser?: boolean;\n  hoistStatic?: boolean;\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n}\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/parse.ts",
    "content": "import {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone } from \"./utils\";\n\ntype OptionalOptions = \"isNativeTag\"; // | TODO:\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>;\n\nexport const defaultParserOptions: MergedParserOptions = {};\n\nexport interface ParserContext {\n  readonly originalSource: string;\n  options: MergedParserOptions;\n  source: string;\n\n  offset: number;\n  line: number;\n  column: number;\n\n  inVPre: boolean;\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = Object.assign({}, defaultParserOptions);\n\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n    inVPre: false,\n  };\n}\n\nexport const baseParse = (content: string, options: ParserOptions = {}): RootNode => {\n  const context = createParserContext(content, options);\n  const children = parseChildren(context, []);\n  return createRoot(children);\n};\n\nfunction parseChildren(context: ParserContext, ancestors: ElementNode[]): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n    if (startsWith(s, \"{{\")) {\n      // Skip mustache when in v-pre\n      if (!context.inVPre) {\n        node = parseInterpolation(context);\n      }\n    } else if (s[0] === \"<\") {\n      if (s[1] === \"!\") {\n        // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n        if (startsWith(s, \"<!--\")) {\n          node = parseComment(context);\n        }\n      } else if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n\n    if (!node) {\n      node = parseText(context);\n    }\n\n    pushNode(nodes, node);\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  if (startsWith(s, \"</\")) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true;\n      }\n    }\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\nfunction parseInterpolation(context: ParserContext): InterpolationNode | undefined {\n  const [open, close] = [\"{{\", \"}}\"];\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength);\n\n  const content = preTrimContent.trim();\n\n  const startOffset = preTrimContent.indexOf(content);\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = [\"<\", \"{{\"];\n\n  let endIndex = context.source.length;\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start); // TODO:\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const children = parseChildren(context, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End); // TODO:\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n  if (tag === \"slot\") {\n    tagType = ElementTypes.SLOT;\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options;\n  if (/^[A-Z]/.test(tag) || (options.isNativeTag && !options.isNativeTag(tag))) {\n    return true;\n  }\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let dirName = match[1] || (startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n      modifiers,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length);\n    } else {\n      content = parseTextData(context, endIndex);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  return rawText;\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/runtimeHelpers.ts",
    "content": "export const FRAGMENT = Symbol();\nexport const CREATE_VNODE = Symbol();\nexport const CREATE_COMMENT = Symbol();\nexport const RESOLVE_COMPONENT = Symbol();\nexport const RENDER_LIST = Symbol();\nexport const RENDER_SLOT = Symbol();\nexport const MERGE_PROPS = Symbol();\nexport const NORMALIZE_CLASS = Symbol();\nexport const NORMALIZE_STYLE = Symbol();\nexport const NORMALIZE_PROPS = Symbol();\nexport const TO_HANDLERS = Symbol();\nexport const TO_HANDLER_KEY = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: \"Fragment\",\n  [CREATE_VNODE]: \"createVNode\",\n  [CREATE_COMMENT]: \"createCommentVNode\",\n  [RESOLVE_COMPONENT]: \"resolveComponent\",\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: \"renderSlot\",\n  [MERGE_PROPS]: \"mergeProps\",\n  [NORMALIZE_CLASS]: \"normalizeClass\",\n  [NORMALIZE_STYLE]: \"normalizeStyle\",\n  [NORMALIZE_PROPS]: \"normalizeProps\",\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: \"toHandlerKey\",\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transform.ts",
    "content": "import { isArray, isString } from \"../shared\";\nimport {\n  ConstantTypes,\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type JSChildNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, helperNameMap } from \"./runtimeHelpers\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null;\n  parent: ParentNode | null;\n  childIndex: number;\n  root: RootNode;\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  identifiers: { [name: string]: number | undefined };\n  hoists: (JSChildNode | null)[];\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  hoist(exp: string | JSChildNode): SimpleExpressionNode;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {}, isBrowser = false }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    currentNode: root,\n    parent: null,\n    childIndex: 0,\n    root,\n    helpers: new Map(),\n    components: new Set(),\n    identifiers: Object.create(null),\n    hoists: [],\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    hoist(exp) {\n      if (isString(exp)) exp = createSimpleExpression(exp);\n      context.hoists.push(exp);\n      const identifier = createSimpleExpression(\n        `_hoisted_${context.hoists.length}`,\n        false,\n        exp.loc,\n        ConstantTypes.CAN_HOIST,\n      );\n      identifier.hoisted = exp;\n      return identifier;\n    },\n    addIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          addId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(addId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          addId(exp.content);\n        }\n      }\n    },\n    removeIdentifiers(exp) {\n      if (!isBrowser) {\n        if (isString(exp)) {\n          removeId(exp);\n        } else if (exp.identifiers) {\n          exp.identifiers.forEach(removeId);\n        } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          removeId(exp.content);\n        }\n      }\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n  createRootCodegen(root, context);\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n  root.hoists = context.hoists;\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {\n  context.currentNode = node;\n\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION:\n      break;\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nfunction hoistStatic(root: RootNode, context: TransformContext) {\n  walk(\n    root,\n    context,\n    // Root node is already transformed\n    isSingleElementRoot(root, root.children[0]),\n  );\n}\n\nfunction isSingleElementRoot(root: RootNode, child: TemplateChildNode): child is ElementNode {\n  return root.children.length === 1 && child.type === NodeTypes.ELEMENT;\n}\n\nfunction walk(node: ParentNode, context: TransformContext, doNotHoistNode: boolean = false) {\n  const { children } = node;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    // only plain elements are eligible for hoisting\n    if (child.type === NodeTypes.ELEMENT && child.tagType === ElementTypes.ELEMENT) {\n      const constantType = doNotHoistNode\n        ? ConstantTypes.NOT_CONSTANT\n        : getConstantType(child, context);\n\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          (child.codegenNode as any).patchFlag = -1 /* HOISTED */;\n          child.codegenNode = context.hoist(child.codegenNode!);\n          continue;\n        }\n      }\n    }\n\n    // walk further\n    if (child.type === NodeTypes.ELEMENT) {\n      walk(child, context);\n    } else if (child.type === NodeTypes.FOR) {\n      // Do not hoist v-for single child\n      walk(child, context, child.children.length === 1);\n    } else if (child.type === NodeTypes.IF) {\n      for (let i = 0; i < child.branches.length; i++) {\n        walk(child.branches[i], context, child.branches[i].children.length === 1);\n      }\n    }\n  }\n}\n\nfunction getConstantType(node: TemplateChildNode, context: TransformContext): ConstantTypes {\n  const { constantCache } = context as any;\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      if (node.tagType !== ElementTypes.ELEMENT) {\n        return ConstantTypes.NOT_CONSTANT;\n      }\n      const cached = constantCache?.get(node);\n      if (cached !== undefined) {\n        return cached;\n      }\n      const codegenNode = node.codegenNode!;\n      if (codegenNode.type !== NodeTypes.VNODE_CALL) {\n        return ConstantTypes.NOT_CONSTANT;\n      }\n      // has dynamic props\n      const flag = getPatchFlag(codegenNode);\n      if (!flag) {\n        let returnType = ConstantTypes.CAN_STRINGIFY;\n\n        // check children\n        for (let i = 0; i < node.children.length; i++) {\n          const childType = getConstantType(node.children[i], context);\n          if (childType === ConstantTypes.NOT_CONSTANT) {\n            constantCache?.set(node, ConstantTypes.NOT_CONSTANT);\n            return ConstantTypes.NOT_CONSTANT;\n          }\n          if (childType < returnType) {\n            returnType = childType;\n          }\n        }\n\n        // check props\n        for (let i = 0; i < node.props.length; i++) {\n          const p = node.props[i];\n          if (p.type === NodeTypes.DIRECTIVE) {\n            constantCache?.set(node, ConstantTypes.NOT_CONSTANT);\n            return ConstantTypes.NOT_CONSTANT;\n          }\n        }\n\n        if (returnType > ConstantTypes.CAN_SKIP_PATCH) {\n          constantCache?.set(node, returnType);\n          return returnType;\n        } else {\n          constantCache?.set(node, ConstantTypes.CAN_SKIP_PATCH);\n          return ConstantTypes.CAN_SKIP_PATCH;\n        }\n      } else {\n        constantCache?.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n    case NodeTypes.TEXT:\n    case NodeTypes.COMMENT:\n      return ConstantTypes.CAN_STRINGIFY;\n    case NodeTypes.IF:\n    case NodeTypes.FOR:\n      return ConstantTypes.NOT_CONSTANT;\n    case NodeTypes.INTERPOLATION:\n      return ConstantTypes.NOT_CONSTANT;\n    default:\n      return ConstantTypes.NOT_CONSTANT;\n  }\n}\n\nfunction getPatchFlag(node: any): number | undefined {\n  const flag = node.patchFlag;\n  return flag ? parseInt(flag, 10) : undefined;\n}\n\nimport { ElementTypes } from \"./ast\";\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/transformElement.ts",
    "content": "import {\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp, toValidAssetId } from \"../utils\";\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);\n  };\n};\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node;\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  props: PropsExpression | undefined;\n  directives: DirectiveNode[];\n} {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n      } else {\n        // TODO: custom directive.\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { makeMap } from \"../../shared/makeMap\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/transformSlotOutlet.ts",
    "content": "import { camelize } from \"../../shared\";\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from \"../ast\";\nimport { RENDER_SLOT } from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from \"../utils\";\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node;\n    const { slotName } = processSlotOutlet(node, context);\n    const slotArgs: CallExpression[\"arguments\"] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ];\n\n    node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);\n  }\n};\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode;\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`;\n\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) slotName = p.exp;\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n\n  return { slotName };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport { type TransformContext, createStructuralDirectiveTransform } from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor = createStructuralDirectiveTransform(\"for\", (node, dir, context) => {\n  return processFor(node, dir, context, (forNode) => {\n    const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n      forNode.source,\n    ]) as ForRenderListExpression;\n\n    forNode.codegenNode = createVNodeCall(\n      context,\n      context.helper(FRAGMENT),\n      undefined,\n      renderExp,\n    ) as ForCodegenNode;\n\n    return () => {\n      const { children } = forNode;\n      const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n      renderExp.arguments.push(\n        createFunctionExpression(\n          createForLoopParams(forNode.parseResult),\n          childBlock,\n          true /* force newline */,\n        ) as ForIteratorExpression,\n      );\n    };\n  });\n});\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers } = context;\n\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  if (!context.isBrowser) {\n    value && addIdentifiers(value);\n    key && addIdentifiers(key);\n    index && addIdentifiers(index);\n  }\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  if (!context.isBrowser) {\n    result.source = processExpression(result.source as SimpleExpressionNode, context);\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n      if (!context.isBrowser) {\n        result.key = processExpression(result.key, context, true);\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n        if (!context.isBrowser) {\n          result.index = processExpression(result.index, context, true);\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    if (!context.isBrowser) {\n      result.value = processExpression(result.value, context, true);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch, context);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode, context: TransformContext): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    if (!context.isBrowser) {\n      isInlineStatement && context.addIdentifiers(`$event`);\n      exp = dir.exp = processExpression(exp, context);\n      isInlineStatement && context.removeIdentifiers(`$event`);\n    }\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `$event => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-core/utils.ts",
    "content": "import {\n  type DirectiveNode,\n  ElementTypes,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SimpleExpressionNode,\n  type SlotOutletNode,\n  type SourceLocation,\n  type TemplateChildNode,\n} from \"./ast\";\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function toValidAssetId(\n  name: string,\n  type: \"component\", // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function isStaticArgOf(arg: DirectiveNode[\"arg\"], name: string): boolean {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\n\nexport function isSlotOutlet(node: RootNode | TemplateChildNode): node is SlotOutletNode {\n  return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-dom/index.ts",
    "content": "import { type CompilerOptions, baseCompile, baseParse } from \"../compiler-core\";\nimport type { DirectiveTransform } from \"../compiler-core/transform\";\nimport { parserOptions } from \"./parserOptions\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\n\nconst DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n};\n\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true };\n  if (option) Object.assign(defaultOption, option);\n  return baseCompile(\n    template,\n    Object.assign({}, parserOptions, defaultOption, {\n      directiveTransforms: DOMDirectiveTransforms,\n    }),\n  );\n}\n\nexport function parse(template: string) {\n  return baseParse(template);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-dom/parserOptions.ts",
    "content": "import type { ParserOptions } from \"../compiler-core\";\nimport { isHTMLTag, isSVGTag } from \"../shared/domTagConfig\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-dom/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-dom/transforms/vOn.ts",
    "content": "import { transformOn as baseTransform } from \"../../compiler-core/transforms/vOn\";\nimport type { DirectiveTransform } from \"../../compiler-core/transform\";\nimport { makeMap } from \"../../shared/makeMap\";\nimport {\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../../runtime-dom/runtimeHelpers\";\nimport { isStaticExp } from \"../../compiler-core/utils\";\nimport { capitalize } from \"../../shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-dom/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../../compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-sfc/compileTemplate.ts",
    "content": "import type { TemplateChildNode } from \"../compiler-core\";\n\nexport interface TemplateCompiler {\n  compile(template: string): string;\n  parse(template: string): { children: TemplateChildNode[] };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-sfc/index.ts",
    "content": "export * from \"./parse\";\nexport * from \"./rewriteDefault\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-sfc/parse.ts",
    "content": "import { type ElementNode, NodeTypes, type SourceLocation } from \"../compiler-core\";\nimport * as CompilerDOM from \"../compiler-dom\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n}\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source);\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        descriptor.script = scriptBlock;\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n\n  let { start, end } = node.loc;\n  start = node.children[0].loc.start;\n  end = node.children[node.children.length - 1].loc.end;\n  const content = source.slice(start.offset, end.offset);\n\n  const loc = { source: content, start, end };\n  const block: SFCBlock = { type, content, loc };\n\n  return block;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/compiler-sfc/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/index.ts",
    "content": "export * from \"./runtime-core\";\nexport * from \"./runtime-dom\";\nexport * from \"./reactivity\";\nexport * from \"./server-renderer\";\nimport { compile } from \"./compiler-dom\";\nimport { type InternalRenderFunction, registerRuntimeCompiler } from \"./runtime-core\";\nimport * as runtimeDom from \"./runtime-dom\";\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/baseHandler.ts",
    "content": "import { hasChanged, isArray, isObject } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, isArray(target) ? \"length\" : ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"../shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/computed.ts",
    "content": "import { isFunction } from \"../shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\ndeclare const ComputedRefSymbol: unique symbol;\n\nexport interface ComputedRef<T = any> extends Ref {\n  readonly value: T;\n  [ComputedRefSymbol]: true;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n\n  public readonly __v_isRef = true;\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run();\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"../shared\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run() {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep) {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]) {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    triggerEffect(effect);\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol) {\n  return targetMap.get(object)?.get(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active() {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on() {\n    activeEffectScope = this;\n  }\n\n  off() {\n    activeEffectScope = this.parent;\n  }\n\n  stop() {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope() {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope() {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/index.ts",
    "content": "export {\n  ref,\n  shallowRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  isRef,\n  customRef,\n  unref,\n  type Ref,\n} from \"./ref\";\nexport { reactive, readonly, isProxy, isReactive, isReadonly } from \"./reactive\";\nexport { ReactiveEffect } from \"./effect\";\nexport {\n  computed,\n  type ComputedRef,\n  type ComputedGetter,\n  type ComputedSetter,\n  type WritableComputedOptions,\n} from \"./computed\";\n\nexport { EffectScope } from \"./effectScope\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/reactive.ts",
    "content": "import { isObject, toRawType } from \"../shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/reactivity/ref.ts",
    "content": "import { type IfAny, isArray } from \"../shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>) {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>) {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref) {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return isReactive(objectWithRefs)\n    ? objectWithRefs\n    : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/apiCreateApp.ts",
    "content": "import type { InjectionKey } from \"./apiInject\";\nimport type { Component } from \"./component\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport { createVNode } from \"./vnode\";\n\nexport interface App<HostElement = any> {\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n\n  // internal, for SSR\n  _component: Component;\n  _props: Record<string, any> | null;\n  _context: AppContext;\n}\n\nexport interface AppContext {\n  app: App;\n  components: Record<string, Component>;\n  provides: Record<string | symbol, any>;\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: {},\n  };\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent, rootProps = null) {\n    const context = createAppContext();\n\n    const app: App = (context.app = {\n      _component: rootComponent,\n      _props: rootProps,\n      _context: context,\n\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent, rootProps, []);\n        vnode.appContext = context;\n        render(vnode, rootContainer);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n\n        return app;\n      },\n\n      component(name: string, component: Component): any {\n        context.components[name] = component;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/apiDefineComponent.ts",
    "content": "import type { EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options: ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>,\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return options as any;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/apiInject.ts",
    "content": "import { currentInstance } from \"./component\";\n\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n) {\n  if (currentInstance) {\n    let provides = currentInstance.provides;\n\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject(key: InjectionKey<any> | string, defaultValue?: unknown) {\n  const instance = currentInstance;\n\n  if (instance) {\n    const provides = instance\n      ? instance.parent == null\n        ? instance.vnode.appContext && instance.vnode.appContext.provides\n        : instance.parent.provides\n      : null;\n\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    } else if (arguments.length > 1) {\n      return defaultValue;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/apiLifecycle.ts",
    "content": "import {\n  type ComponentInternalInstance,\n  currentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n} from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(args);\n      unsetCurrentInstance();\n      return res;\n    };\n\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (hook: T, target: ComponentInternalInstance | null = currentInstance) =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\nexport const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);\n\n// KeepAlive lifecycle hooks\nexport const onActivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.a || (target.a = []);\n    hooks.push(hook);\n  }\n};\n\nexport const onDeactivated = (\n  hook: () => void,\n  target: ComponentInternalInstance | null = currentInstance,\n) => {\n  if (target) {\n    const hooks = target.da || (target.da = []);\n    hooks.push(hook);\n  }\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"../reactivity\";\nimport { hasChanged, isArray, isFunction, isMap, isObject, isPlainObject, isSet } from \"../shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n) {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect) {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>) {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/component.ts",
    "content": "import { EffectScope, type ReactiveEffect } from \"../reactivity\";\nimport { proxyRefs } from \"../reactivity/ref\";\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type Props, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  type ComponentPublicInstanceConstructor,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type ConcreteComponent<Props = {}, RawBindings = any, D = any> = ComponentOptions<\n  Props,\n  RawBindings,\n  D\n>;\n\nexport type Component<P = any> = ConcreteComponent<P> | ComponentPublicInstanceConstructor<P>;\n\nexport type Data = Record<string, unknown>;\n\ntype LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  parent: ComponentInternalInstance | null;\n  appContext: AppContext;\n\n  vnode: VNode;\n  subTree: VNode;\n  next: VNode | null;\n\n  effect: ReactiveEffect;\n  render: InternalRenderFunction;\n  update: () => void;\n\n  provides: Data;\n  scope: EffectScope;\n\n  propsOptions: Props;\n  props: Data;\n  attrs: Data;\n  slots: InternalSlots;\n  emit: (event: string, ...args: any[]) => void;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  proxy: ComponentPublicInstance | null;\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n\n  ctx: Data;\n\n  data: Data;\n  computed: Data;\n\n  isMounted: boolean;\n  isDeactivated: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n\n  // KeepAlive lifecycle hooks\n  a: LifecycleHook; // activated\n  da: LifecycleHook; // deactivated\n}\n\nexport type InternalRenderFunction = {\n  (ctx: ComponentPublicInstance): VNodeChild;\n};\n\nconst emptyAppContext = createAppContext();\n\nlet uid = 0;\n\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    appContext,\n\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    scope: new EffectScope(),\n\n    propsOptions: type.props || {},\n    props: {},\n    attrs: {},\n    slots: {},\n    emit: null!, // to be set immediately\n\n    setupState: {},\n    setupContext: null,\n\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n\n    data: {},\n    computed: {},\n\n    isMounted: false,\n    isDeactivated: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n\n    // KeepAlive lifecycle hooks\n    a: null,\n    da: null,\n  };\n\n  instance.emit = emit.bind(null, instance);\n\n  instance.ctx = { _: instance };\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | null = null;\n\nexport const getCurrentInstance = (): ComponentInternalInstance | null => {\n  return currentInstance;\n};\n\nexport const setCurrentInstance = (instance: ComponentInternalInstance | null) => {\n  const prev = currentInstance;\n  currentInstance = instance;\n  if (instance) {\n    instance.scope.on();\n  }\n  return prev;\n};\n\nexport const unsetCurrentInstance = (prev: ComponentInternalInstance | null = null) => {\n  currentInstance && currentInstance.scope.off();\n  currentInstance = prev;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const { setup, render, template } = instance.type as ConcreteComponent;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  if (setup) {\n    const prev = setCurrentInstance(instance);\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    const setupResult = setup(instance.props, setupContext) as InternalRenderFunction;\n    unsetCurrentInstance(prev);\n\n    if (typeof setupResult === \"function\") {\n      instance.render = setupResult;\n    } else if (typeof setupResult === \"object\" && setupResult !== null) {\n      instance.setupState = proxyRefs(setupResult);\n    } else {\n      // do nothing\n    }\n  }\n\n  if (compile && !render) {\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (render) {\n    instance.render = render as InternalRenderFunction;\n  }\n\n  const prev = setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance(prev);\n};\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return {\n    slots: instance.slots,\n    emit: instance.emit,\n    expose,\n  };\n}\n\nexport function getExposeProxy(instance: ComponentInternalInstance) {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]) {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/componentOptions.ts",
    "content": "import {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\nimport type { Component, ComponentInternalInstance, Data, SetupContext } from \"./component\";\nimport type { EmitsOptions } from \"./componentEmits\";\nimport type { PropType } from \"./componentProps\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { VNode } from \"./vnode\";\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: (E | EE[]) & ThisType<void>;\n  slots?: S;\n\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  template?: string;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  components?: Record<string, Component>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key, opt.default);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n) {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/componentProps.ts",
    "content": "import { reactive } from \"../reactivity\";\nimport { camelize, hasOwn } from \"../shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type Props = Record<string, PropOptions | null>;\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null) {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"../shared\";\nimport { type ComponentInternalInstance, type Data, getExposeProxy } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport { nextTick, queueJob } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { setupState, props, data, ctx } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(data, key)) {\n      return instance.data[key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n\n    const publicGetter = publicPropertiesMap[key];\n    if (publicGetter) {\n      return publicGetter(instance);\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { setupState, data } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      instance.data[key] = value;\n      return true;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions, data } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(data, key) ||\n      hasOwn(ctx, key) ||\n      hasOwn(publicPropertiesMap, key)\n    );\n  },\n};\n\nexport type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>;\n\nconst getPublicInstance = (\n  i: ComponentInternalInstance | null,\n): ComponentPublicInstance | ComponentInternalInstance[\"exposed\"] | null => {\n  if (!i) return null;\n  return getExposeProxy(i) || i.proxy;\n};\n\nexport const publicPropertiesMap: PublicPropertiesMap = {\n  $: (i) => i,\n  $el: (i) => i.vnode.el,\n  $data: (i) => i.data,\n  $props: (i) => i.props,\n  $slots: (i) => i.slots,\n  $parent: (i) => getPublicInstance(i.parent),\n  $emit: (i) => i.emit,\n  $forceUpdate: (i) => () => queueJob(i.update),\n  $nextTick: (i) => nextTick.bind(i.proxy!),\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/componentSlots.ts",
    "content": "import { toRaw } from \"../reactivity/reactive\";\nimport type { IfAny, Prettify } from \"../shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n) => {\n  instance.slots = toRaw(children as InternalSlots);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/components/KeepAlive.ts",
    "content": "import { ShapeFlags, isArray, isString } from \"../../shared\";\nimport type { ComponentInternalInstance, Data } from \"../component\";\nimport { getCurrentInstance } from \"../component\";\nimport type { VNode } from \"../vnode\";\nimport { isSameVNodeType } from \"../vnode\";\nimport { queuePostFlushCb } from \"../scheduler\";\n\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n\ninterface KeepAliveRenderer {\n  p: (\n    n1: VNode | null,\n    n2: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  m: (vnode: VNode, container: any, anchor: any | null) => void;\n  um: (vnode: VNode) => void;\n  o: {\n    createElement: (type: string) => any;\n  };\n}\n\nconst KeepAliveImpl = {\n  name: `KeepAlive`,\n  __isKeepAlive: true,\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number],\n  },\n  setup(props: KeepAliveProps, { slots }: { slots: any }) {\n    const instance = getCurrentInstance()! as KeepAliveContext;\n    const cache: Map<any, VNode> = new Map();\n    const keys: Set<any> = new Set();\n    let current: VNode | null = null;\n\n    const parentComponent = instance.parent as ComponentInternalInstance;\n\n    // Create a hidden container for holding deactivated components\n    const storageContainer = instance.renderer.o.createElement(\"div\");\n\n    instance.activate = (vnode, container, anchor, _parentComponent) => {\n      const instance = vnode.component!;\n      move(vnode, container, anchor);\n      // in case props have changed\n      patch(instance.vnode, vnode, container, anchor, parentComponent);\n      queuePostFlushCb(() => {\n        instance.isDeactivated = false;\n        if (instance.a) {\n          instance.a.forEach((hook: () => void) => hook());\n        }\n      });\n    };\n\n    instance.deactivate = (vnode: VNode) => {\n      move(vnode, storageContainer, null);\n      queuePostFlushCb(() => {\n        const instance = vnode.component!;\n        if (instance.da) {\n          instance.da.forEach((hook: () => void) => hook());\n        }\n        instance.isDeactivated = true;\n      });\n    };\n\n    function move(vnode: VNode, container: any, anchor: any | null): void {\n      instance.renderer.m(vnode, container, anchor);\n    }\n\n    function patch(\n      n1: VNode | null,\n      n2: VNode,\n      container: any,\n      anchor: any | null,\n      parentComponent: ComponentInternalInstance | null,\n    ): void {\n      instance.renderer.p(n1, n2, container, anchor, parentComponent);\n    }\n\n    function unmount(vnode: VNode): void {\n      resetShapeFlag(vnode);\n      instance.renderer.um(vnode);\n    }\n\n    function pruneCacheEntry(key: any): void {\n      const cached = cache.get(key) as VNode;\n      if (!current || !isSameVNodeType(cached, current)) {\n        unmount(cached);\n      } else if (current) {\n        resetShapeFlag(current);\n      }\n      cache.delete(key);\n      keys.delete(key);\n    }\n\n    return (): VNode | undefined => {\n      if (!slots.default) {\n        return undefined;\n      }\n\n      const children = slots.default();\n      const rawVNode = children[0];\n      if (children.length > 1) {\n        current = null;\n        return children as unknown as VNode;\n      } else if (\n        !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n        !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n      ) {\n        current = null;\n        return rawVNode;\n      }\n\n      let vnode = rawVNode;\n      const comp = vnode.type as any;\n      const name = getComponentName(comp);\n      const { include, exclude, max } = props;\n\n      if (\n        (include && (!name || !matches(include, name))) ||\n        (exclude && name && matches(exclude, name))\n      ) {\n        current = vnode;\n        return rawVNode;\n      }\n\n      const key = vnode.key == null ? comp : vnode.key;\n      const cachedVNode = cache.get(key);\n\n      if (cachedVNode) {\n        vnode.el = cachedVNode.el;\n        vnode.component = cachedVNode.component;\n        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n        keys.delete(key);\n        keys.add(key);\n      } else {\n        keys.add(key);\n        if (max && keys.size > parseInt(max as string, 10)) {\n          pruneCacheEntry(keys.values().next().value);\n        }\n      }\n\n      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n      current = vnode;\n      return vnode;\n    };\n  },\n};\n\nexport const KeepAlive = KeepAliveImpl as any as {\n  __isKeepAlive: true;\n  new (): {\n    $props: KeepAliveProps;\n  };\n};\n\nexport function isKeepAlive(vnode: VNode): boolean {\n  return (vnode.type as any).__isKeepAlive === true;\n}\n\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n\nfunction getComponentName(comp: { name?: string; __name?: string }): string | undefined {\n  return comp.name || comp.__name;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/h.ts",
    "content": "import type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { type Fragment, type VNodeProps, createVNode } from \"./vnode\";\n\nexport function h(\n  type: string | ComponentPublicInstance | typeof Fragment,\n  props: VNodeProps,\n  children: any,\n) {\n  return createVNode(type, props, children);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/helpers/renderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\nimport type { VNode, VNodeChild } from \"../vnode\";\n\n/**\n * v-for string\n */\nexport function renderList(\n  source: string,\n  renderItem: (value: string, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for number\n */\nexport function renderList(\n  source: number,\n  renderItem: (value: number, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for array\n */\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for iterable\n */\nexport function renderList<T>(\n  source: Iterable<T>,\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * v-for object\n */\nexport function renderList<T>(\n  source: T,\n  renderItem: <K extends keyof T>(value: T[K], key: K, index: number) => VNodeChild,\n): VNodeChild[];\n\n/**\n * Actual implementation\n */\nexport function renderList(\n  source: any,\n  renderItem: (...args: any[]) => VNodeChild,\n  cache?: any[],\n  index?: number,\n): VNodeChild[] {\n  let ret: VNodeChild[];\n  const cached = (cache && cache[index!]) as VNode[] | undefined;\n\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, undefined, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      ret = Array.from(source as Iterable<any>, (item, i) =>\n        renderItem(item, i, undefined, cached && cached[i]),\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n\n  if (cache) {\n    cache[index!] = ret;\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/helpers/renderSlot.ts",
    "content": "import { Fragment, type VNode, createVNode } from \"../vnode\";\nimport type { Slots } from \"../componentSlots\";\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name];\n  if (!slot) {\n    slot = () => [];\n  }\n\n  return createVNode(Fragment, {}, slot());\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/helpers/resolveAssets.ts",
    "content": "import { camelize, capitalize } from \"../../shared\";\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name);\n    return res;\n  }\n\n  return name;\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"../../shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/index.ts",
    "content": "export {\n  camelize,\n  capitalize,\n  toHandlerKey,\n  normalizeProps,\n  normalizeClass,\n  normalizeStyle,\n} from \"../shared\";\n\nexport type { App, CreateAppFunction } from \"./apiCreateApp\";\nexport { createAppAPI } from \"./apiCreateApp\";\n\nexport { defineComponent } from \"./apiDefineComponent\";\n\nexport {\n  registerRuntimeCompiler,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  getCurrentInstance,\n  type InternalRenderFunction,\n  type ComponentInternalInstance,\n  type Component,\n} from \"./component\";\n\nexport { KeepAlive } from \"./components/KeepAlive\";\nexport { type ComponentOptions } from \"./componentOptions\";\n\nexport { type PropType } from \"./componentProps\";\n\nexport type { RendererOptions } from \"./renderer\";\nexport { createRenderer } from \"./renderer\";\nexport { h } from \"./h\";\n\nexport { nextTick } from \"./scheduler\";\n\nexport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  onActivated,\n  onDeactivated,\n} from \"./apiLifecycle\";\n\nexport { watch, watchEffect } from \"./apiWatch\";\n\nexport { provide, inject, type InjectionKey } from \"./apiInject\";\n\nexport {\n  createVNode,\n  createCommentVNode,\n  normalizeVNode,\n  mergeProps,\n  isVNode,\n  Fragment,\n  Text,\n  Comment,\n  type VNode,\n  type VNodeProps,\n  type VNodeArrayChildren,\n  type DirectiveBinding,\n} from \"./vnode\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\nexport { renderList } from \"./helpers/renderList\";\nexport { renderSlot } from \"./helpers/renderSlot\";\nexport { resolveComponent } from \"./helpers/resolveAssets\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/renderer.ts",
    "content": "import { ReactiveEffect } from \"../reactivity\";\nimport { invokeArrayFns } from \"../shared\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport {\n  type ComponentInternalInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { type KeepAliveContext, isKeepAlive } from \"./components/KeepAlive\";\nimport { updateProps } from \"./componentProps\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport { type SchedulerJob, flushPostFlushCbs, queueJob, queuePostFlushCb } from \"./scheduler\";\nimport { Comment, Fragment, Text, type VNode, isSameVNodeType, normalizeVNode } from \"./vnode\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[],\n    unmountChildren?: (children: VNode<HostNode>[]) => void,\n  ): void;\n\n  createElement(type: string): HostElement;\n\n  createText(text: string): HostNode;\n\n  createComment(text: string): HostNode;\n\n  setText(node: HostNode, text: string): void;\n\n  setElementText(node: HostNode, text: string): void;\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void;\n\n  remove(child: HostNode): void;\n\n  parentNode(node: HostNode): HostNode | null;\n\n  nextSibling(node: HostNode): HostNode | null;\n}\n\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\nexport function createRenderer(options: RendererOptions) {\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    setText: hostSetText,\n    createComment: hostCreateComment,\n    setElementText: hostSetElementText,\n    insert: hostInsert,\n    remove: hostRemove,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const { type, ref, shapeFlag } = n2;\n    if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, anchor, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag } = vnode;\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNode[], el, null, parentComponent);\n    }\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key], vnode.children as VNode[], unmountChildren);\n      }\n    }\n\n    hostInsert(el, container, anchor);\n  };\n\n  const mountChildren = (\n    children: VNode[],\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n\n    const oldProps = n1.props || {};\n    const newProps = n2.props || {};\n\n    patchChildren(n1, n2, el, anchor, parentComponent);\n\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const patchChildren = (\n    n1: VNode,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNode[], container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    const e1 = c1.length - 1;\n    const e2 = l2 - 1;\n\n    const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n    for (i = 0; i <= e2; i++) {\n      const nextChild = (c2[i] = normalizeVNode(c2[i]));\n      if (nextChild.key != null) {\n        keyToNewIndexMap.set(nextChild.key, i);\n      }\n    }\n\n    let j;\n    let patched = 0;\n    const toBePatched = e2 + 1;\n    let moved = false;\n    let maxNewIndexSoFar = 0;\n\n    const newIndexToOldIndexMap = new Array(toBePatched);\n    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n    for (i = 0; i <= e1; i++) {\n      const prevChild = c1[i];\n      if (patched >= toBePatched) {\n        // all new children have been patched so this can only be a removal\n        unmount(prevChild);\n        continue;\n      }\n      let newIndex;\n      if (prevChild.key != null) {\n        newIndex = keyToNewIndexMap.get(prevChild.key);\n      } else {\n        // key-less node, try to locate a key-less node of the same type\n        for (j = 0; j <= e2; j++) {\n          if (newIndexToOldIndexMap[j] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n            newIndex = j;\n            break;\n          }\n        }\n      }\n      if (newIndex === undefined) {\n        unmount(prevChild);\n      } else {\n        newIndexToOldIndexMap[newIndex] = i + 1;\n        if (newIndex >= maxNewIndexSoFar) {\n          maxNewIndexSoFar = newIndex;\n        } else {\n          moved = true;\n        }\n        patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n        patched++;\n      }\n    }\n\n    const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n    j = increasingNewIndexSequence.length - 1;\n    for (i = toBePatched - 1; i >= 0; i--) {\n      const nextIndex = i;\n      const nextChild = c2[nextIndex] as VNode;\n      const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n      if (newIndexToOldIndexMap[i] === 0) {\n        // mount new\n        patch(null, nextChild, container, anchor, parentComponent);\n      } else if (moved) {\n        // move if:\n        // There is no stable subsequence (e.g. a reverse)\n        // OR current node is not among the stable sequence\n        if (j < 0 || i !== increasingNewIndexSequence[j]) {\n          move(nextChild, container, anchor);\n        } else {\n          j--;\n        }\n      }\n    }\n  };\n\n  const move = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor);\n  };\n\n  const unmount = (vnode: VNode, parentComponent?: ComponentInternalInstance | null) => {\n    const { children, shapeFlag } = vnode;\n\n    // KeepAlive: deactivate instead of unmount\n    if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n      (parentComponent as KeepAliveContext).deactivate(vnode);\n      return;\n    }\n\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (Array.isArray(children)) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove = (vnode: VNode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren = (children: VNode[]) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const processText = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n        // Restore from cache\n        (parentComponent as KeepAliveContext).activate(n2, container, anchor, parentComponent);\n      } else {\n        mountComponent(n2, container, anchor, parentComponent);\n      }\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(n2.children as VNode[], container, fragmentEndAnchor, parentComponent);\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountComponent = (\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));\n\n    // Inject renderer internals for KeepAlive\n    if (isKeepAlive(initialVNode)) {\n      (instance as KeepAliveContext).renderer = {\n        p: patch,\n        m: move,\n        um: unmount,\n        o: {\n          createElement: hostCreateElement,\n        },\n      };\n    }\n\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    const componentUpdateFn = () => {\n      const { render, bm, m, bu, u, proxy } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        const prev = setCurrentRenderingInstance(instance);\n        const subTree = (instance.subTree = normalizeVNode(render(proxy!)));\n        setCurrentRenderingInstance(prev);\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          next.component = instance;\n          instance.vnode = next;\n          instance.next = null;\n          updateProps(instance, next.props);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = normalizeVNode(render(proxy!));\n        instance.subTree = nextTree;\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n    update();\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const render: RootRenderFunction = (vnode, container) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch(null, vnode, container, null, null);\n    }\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n\n  return { render };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport { getExposeProxy } from \"./component\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode) {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = getExposeProxy(vnode.component!) || vnode.component!.proxy;\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/scheduler.ts",
    "content": "import { isArray } from \"../shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nexport function queueJob(job: SchedulerJob) {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(() => {\n      isFlushPending = false;\n      isFlushing = true;\n      queue.forEach((job) => {\n        job();\n      });\n\n      flushIndex = 0;\n      queue.length = 0;\n      flushPostFlushCbs();\n      isFlushing = false;\n      currentFlushPromise = null;\n    });\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPostFlushCbs() {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-core/vnode.ts",
    "content": "import type { Ref } from \"../reactivity\";\nimport { isArray, isFunction, isObject, isString } from \"../shared\";\nimport { normalizeClass, normalizeStyle } from \"../shared/normalizeProp\";\nimport { ShapeFlags } from \"../shared/shapeFlags\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\n\nimport type { RawSlots } from \"./componentSlots\";\n\nexport const Fragment = Symbol();\nexport const Text = Symbol();\nexport const Comment = Symbol();\n\nexport type VNodeTypes = string | Component | typeof Text | typeof Comment | typeof Fragment;\n\nexport interface TransitionHooks<HostElement = any> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nexport interface VNode<HostNode = any> {\n  __v_isVNode: true;\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  component: ComponentInternalInstance | null;\n  shapeFlag: number;\n\n  // optimization only\n  patchFlag: number;\n  dynamicProps: string[] | null;\n  dynamicChildren: VNode[] | null;\n\n  // application root node only\n  appContext: AppContext | null;\n\n  // directives\n  dirs?: DirectiveBinding[] | null;\n\n  // transition hooks\n  transition?: TransitionHooks<HostNode> | null;\n}\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentInternalInstance | null;\n  value: V;\n  oldValue: V | null;\n  arg?: string;\n  modifiers: Record<string, boolean>;\n  dir: ObjectDirective<any, V>;\n}\n\nexport interface ObjectDirective<T = any, V = any> {\n  created?: DirectiveHook<T, null, V>;\n  beforeMount?: DirectiveHook<T, null, V>;\n  mounted?: DirectiveHook<T, null, V>;\n  beforeUpdate?: DirectiveHook<T, VNode<T>, V>;\n  updated?: DirectiveHook<T, VNode<T>, V>;\n  beforeUnmount?: DirectiveHook<T, null, V>;\n  unmounted?: DirectiveHook<T, null, V>;\n  getSSRProps?: (binding: DirectiveBinding<V>, vnode: VNode) => Record<string, unknown> | undefined;\n}\n\nexport type DirectiveHook<T = any, Prev = VNode<T> | null, V = any> = (\n  el: T,\n  binding: DirectiveBinding<V>,\n  vnode: VNode<T>,\n  prevVNode: Prev,\n) => void;\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: any,\n  patchFlag: number = 0,\n  dynamicProps: string[] | null = null,\n): VNode {\n  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : isObject(type) ? ShapeFlags.COMPONENT : 0;\n\n  const vnode: VNode = {\n    __v_isVNode: true,\n    type,\n    props,\n    children: children,\n    el: undefined,\n    anchor: null,\n    key: props?.key ?? null,\n    ref: props?.ref ?? null,\n    component: null,\n    shapeFlag,\n    patchFlag,\n    dynamicProps,\n    dynamicChildren: null,\n    appContext: null,\n    dirs: null,\n  };\n\n  normalizeChildren(vnode, children);\n\n  return vnode;\n}\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (child == null || typeof child === \"boolean\") {\n    return createVNode(Comment, null, \"\");\n  } else if (isArray(child)) {\n    return createVNode(Fragment, null, child.slice());\n  } else if (typeof child === \"object\") {\n    return cloneIfMounted(child as VNode);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nfunction cloneIfMounted(child: VNode): VNode {\n  return child.el === null ? child : ({ ...child, __v_isVNode: true } as VNode);\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport function isVNode(value: any): value is VNode {\n  return value ? value.__v_isVNode === true : false;\n}\n\nexport function cloneVNode(vnode: VNode, extraProps?: VNodeProps | null): VNode {\n  const { props, children } = vnode;\n  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;\n  const cloned: VNode = {\n    __v_isVNode: true,\n    type: vnode.type,\n    props: mergedProps,\n    key: mergedProps?.key ?? vnode.key,\n    ref: mergedProps?.ref ?? vnode.ref,\n    children: children,\n    component: vnode.component,\n    el: vnode.el,\n    anchor: vnode.anchor,\n    shapeFlag: vnode.shapeFlag,\n    appContext: vnode.appContext,\n    dirs: vnode.dirs,\n  };\n  return cloned;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]) {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/components/Transition.ts",
    "content": "import type { VNode } from \"../../runtime-core\";\n\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n}\n\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nconst TRANSITION = \"transition\";\nconst ANIMATION = \"animation\";\n\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function resolveTransitionProps(\n  rawProps: TransitionProps,\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    leaveFromClass = `${name}-leave-from`,\n    leaveActiveClass = `${name}-leave-active`,\n    leaveToClass = `${name}-leave-to`,\n    mode = \"default\",\n    onBeforeEnter,\n    onEnter,\n    onAfterEnter,\n    onBeforeLeave,\n    onLeave,\n    onAfterLeave,\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  const finishEnter = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, enterToClass);\n    removeTransitionClass(el, enterActiveClass);\n    done && done();\n    onAfterEnter && onAfterEnter(el);\n  };\n\n  const finishLeave = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, leaveToClass);\n    removeTransitionClass(el, leaveActiveClass);\n    done && done();\n    onAfterLeave && onAfterLeave(el);\n  };\n\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      onBeforeEnter && onBeforeEnter(el);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter(el) {\n      const resolve = () => finishEnter(el);\n      onEnter && onEnter(el, resolve);\n      nextFrame(() => {\n        removeTransitionClass(el, enterFromClass);\n        addTransitionClass(el, enterToClass);\n        if (!hasExplicitCallback(onEnter)) {\n          whenTransitionEnds(el, type, enterDuration, resolve);\n        }\n      });\n    },\n    leave(el, done) {\n      const resolve = () => finishLeave(el, done);\n      onBeforeLeave && onBeforeLeave(el);\n      addTransitionClass(el, leaveFromClass);\n      forceReflow();\n      addTransitionClass(el, leaveActiveClass);\n      nextFrame(() => {\n        removeTransitionClass(el, leaveFromClass);\n        addTransitionClass(el, leaveToClass);\n        if (!hasExplicitCallback(onLeave)) {\n          whenTransitionEnds(el, type, leaveDuration, resolve);\n        }\n      });\n      onLeave && onLeave(el, resolve);\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n\nfunction normalizeDuration(duration: TransitionProps[\"duration\"]): [number, number] | null {\n  if (duration == null) {\n    return null;\n  } else if (typeof duration === \"object\") {\n    return [NumberOf(duration.enter), NumberOf(duration.leave)];\n  } else {\n    const n = NumberOf(duration);\n    return [n, n];\n  }\n}\n\nfunction NumberOf(val: unknown): number {\n  const res = Number(val);\n  return isNaN(res) ? 0 : res;\n}\n\nexport function addTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n\nfunction hasExplicitCallback(hook: ((el: Element, done: () => void) => void) | undefined): boolean {\n  return hook ? hook.length > 1 : false;\n}\n\nexport function forceReflow(): void {\n  document.body.offsetHeight;\n}\n\ninterface CSSTransitionInfo {\n  type: typeof TRANSITION | typeof ANIMATION | null;\n  propCount: number;\n  timeout: number;\n  hasTransform: boolean;\n}\n\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"],\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n  const getStyleProperties = (key: keyof CSSStyleDeclaration) => (styles[key] || \"\") as string;\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION;\n      timeout = animationTimeout;\n      propCount = animationDurations.length;\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null;\n    propCount = type\n      ? type === TRANSITION\n        ? transitionDurations.length\n        : animationDurations.length\n      : 0;\n  }\n\n  const hasTransform =\n    type === TRANSITION && /\\b(transform|all)(,|$)/.test(getStyleProperties(\"transitionProperty\"));\n\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform,\n  };\n}\n\nfunction getTimeout(delays: string[], durations: string[]): number {\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays);\n  }\n  return Math.max(...durations.map((d, i) => toMs(d) + toMs(delays[i])));\n}\n\nfunction toMs(s: string): number {\n  return Number(s.slice(0, -1).replace(\",\", \".\")) * 1000;\n}\n\nlet endId = 0;\n\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void,\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout) as unknown as void;\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\";\n  let ended = 0;\n\n  const end = () => {\n    el.removeEventListener(endEvent, onEnd);\n    resolveIfNotStale();\n  };\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n\n// Transition component wrapper\nexport const Transition = {\n  name: \"Transition\",\n  props: {\n    name: String,\n    type: String,\n    css: { type: Boolean, default: true },\n    duration: [Number, Object],\n    enterFromClass: String,\n    enterActiveClass: String,\n    enterToClass: String,\n    leaveFromClass: String,\n    leaveActiveClass: String,\n    leaveToClass: String,\n    mode: String,\n    onBeforeEnter: Function,\n    onEnter: Function,\n    onAfterEnter: Function,\n    onBeforeLeave: Function,\n    onLeave: Function,\n    onAfterLeave: Function,\n  },\n  setup(props: TransitionProps, { slots }: { slots: any }) {\n    return () => {\n      const children = slots.default && slots.default();\n      if (!children || !children.length) {\n        return undefined;\n      }\n\n      const child = children[0];\n      if (child) {\n        const innerProps = resolveTransitionProps(props);\n        child.transition = innerProps;\n      }\n      return child;\n    };\n  },\n} as any;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/directives/vOn.ts",
    "content": "import { hyphenate } from \"../../shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (fn: Function, modifiers: string[]) => {\n  return (event: KeyboardEvent) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/index.ts",
    "content": "import { type CreateAppFunction, createAppAPI, createRenderer } from \"../runtime-core\";\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst { render } = createRenderer({ ...nodeOps, patchProp });\nconst _createApp = createAppAPI(render);\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n    mount(container);\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n\nexport * from \"../runtime-core\";\nexport * from \"./directives/vOn\";\nexport { Transition } from \"./components/Transition\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any) {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/modules/class.ts",
    "content": "export function patchClass(el: Element, value: string | null) {\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else {\n    el.className = value;\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener) {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener) {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value;\n  } else {\n    const name = parseName(rawName);\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/modules/props.ts",
    "content": "export function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  // the following args are passed only due to potential innerHTML/textContent\n  // overriding existing VNodes, in which case the old tree must be properly\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (prevChildren) {\n      unmountChildren(prevChildren);\n    }\n    el[key] = value == null ? \"\" : value;\n    return;\n  }\n\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      // e.g. <select multiple> compiles to { multiple: '' }\n      value = !!value || value === \"\";\n    } else if (value == null && type === \"string\") {\n      // e.g. <div :id=\"null\">\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      // e.g. <img :width=\"null\">\n      value = 0;\n      needRemove = true;\n    }\n  }\n\n  try {\n    el[key] = value;\n  } catch (e: any) {}\n\n  needRemove && el.removeAttribute(key);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/modules/style.ts",
    "content": "import { isArray, isString } from \"../../shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style) {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/nodeOps.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions<Node, Element>, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text) => document.createComment(text),\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText(node, text) {\n    node.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => node.nextSibling,\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/patchProp.ts",
    "content": "import type { RendererOptions } from \"../runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchEvent } from \"./modules/events\";\nimport { patchDOMProp } from \"./modules/props\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string) => onRE.test(key);\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n\nfunction shouldSetAsProp(el: Element, key: string) {\n  // these are enumerated attrs, however their corresponding DOM properties\n  // are actually booleans - this leads to setting it with a string \"false\"\n  // value leading it to be coerced to `true`, so we need to always treat\n  // them as attributes.\n  // Note that `contentEditable` doesn't have this problem: its DOM\n  // property is also enumerated string values.\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n\n  // form property on form elements is readonly and must be set as\n  // attribute.\n  if (key === \"form\") {\n    return false;\n  }\n\n  // <input list> must be set as attribute\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n\n  // <textarea type> must be set as attribute\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n\n  return key in el;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/runtime-dom/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"../compiler-core/runtimeHelpers\";\n\nexport const V_ON_WITH_MODIFIERS = Symbol();\nexport const V_ON_WITH_KEYS = Symbol();\n\nregisterRuntimeHelpers({\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/helpers/ssrInterpolate.ts",
    "content": "import { toDisplayString } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrInterpolate(value: unknown): string {\n  return escapeHtml(toDisplayString(value));\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/helpers/ssrRenderAttrs.ts",
    "content": "import { isArray, isFunction, isOn, isString, normalizeClass, normalizeStyle } from \"../../shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrRenderAttrs(props: Record<string, unknown>, tag?: string): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (ssrIsIgnoredKey(key) || isOn(key) || (tag === \"textarea\" && key === \"value\")) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return key === \"key\" || key === \"ref\" || key === \"innerHTML\" || key === \"textContent\";\n}\n\nexport function ssrRenderDynamicAttr(key: string, value: unknown, tag?: string): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag)) ? key : propsToAttrMap[key] || key.toLowerCase();\n\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\" ? ` ${attrKey}` : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(`[server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`);\n    return \"\";\n  }\n}\n\nexport function ssrRenderAttr(key: string, value: unknown): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  return ` ${key}=\"${escapeHtml(value)}\"`;\n}\n\nfunction isRenderableAttrValue(value: unknown): boolean {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(styles: Record<string, string | number> | null): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n\nfunction hyphenate(str: string): string {\n  return str.replace(/\\B([A-Z])/g, \"-$1\").toLowerCase();\n}\n\n// Maps props to their corresponding HTML attribute names\nconst propsToAttrMap: Record<string, string> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n\n// Boolean attributes\nconst isBooleanAttr = (key: string): boolean => booleanAttrsSet.has(key);\n\nconst booleanAttrsSet = new Set(\n  (\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n    \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n    \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n    \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n    \"required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"\n  ).split(\",\"),\n);\n\n// Checks if the attribute name is safe for SSR\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nfunction isSSRSafeAttrName(name: string): boolean {\n  return !unsafeAttrCharRE.test(name);\n}\n\n// SVG tags\nconst SVG_TAGS = new Set(\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\".split(\n    \",\",\n  ),\n);\n\nfunction isSVGTag(tag: string): boolean {\n  return SVG_TAGS.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/helpers/ssrRenderList.ts",
    "content": "import { isArray, isObject, isString } from \"../../shared\";\n\nexport function ssrRenderList(\n  source: unknown,\n  renderItem: (value: unknown, key: string | number, index?: number) => void,\n): void {\n  if (isArray(source) || isString(source)) {\n    for (let i = 0, l = source.length; i < l; i++) {\n      renderItem(source[i], i);\n    }\n  } else if (typeof source === \"number\") {\n    for (let i = 0; i < source; i++) {\n      renderItem(i + 1, i);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      const arr = Array.from(source as Iterable<any>);\n      for (let i = 0, l = arr.length; i < l; i++) {\n        renderItem(arr[i], i);\n      }\n    } else {\n      const keys = Object.keys(source);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        renderItem((source as Record<string, unknown>)[key], key, i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/helpers/ssrUtils.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n\n// void elements\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nconst isVoidTagSet = new Set(VOID_TAGS.split(\",\"));\nexport function isVoidTag(tag: string): boolean {\n  return isVoidTagSet.has(tag);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/index.ts",
    "content": "// public\nexport type { SSRContext } from \"./render\";\nexport { renderToString } from \"./renderToString\";\n\n// internal runtime helpers\nexport { ssrInterpolate } from \"./helpers/ssrInterpolate\";\nexport { ssrRenderList } from \"./helpers/ssrRenderList\";\nexport {\n  ssrRenderAttrs,\n  ssrRenderClass,\n  ssrRenderStyle,\n  ssrRenderAttr,\n  ssrRenderDynamicAttr,\n} from \"./helpers/ssrRenderAttrs\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/render.ts",
    "content": "import {\n  Comment,\n  type Component,\n  type ComponentInternalInstance,\n  type DirectiveBinding,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  type VNodeProps,\n  mergeProps,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  normalizeVNode,\n} from \"../runtime-core\";\nimport { ShapeFlags, isArray, isFunction, isPromise, isString } from \"../shared\";\nimport { ssrRenderAttrs } from \"./helpers/ssrRenderAttrs\";\nimport { escapeHtml, escapeHtmlComment, isVoidTag } from \"./helpers/ssrUtils\";\n\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\nexport type Props = Record<string, unknown>;\n\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  /**\n   * @internal\n   */\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  /**\n   * @internal\n   */\n  __watcherHandles?: (() => void)[];\n};\n\n// Each component has a buffer array.\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = (vnode.component = createComponentInstance(vnode, parentComponent, null as any));\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() => renderComponentSubTree(instance));\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    const root = (comp as Function)(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`);\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      }\n  }\n}\n\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const {\n      dir: { getSSRProps },\n    } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/server-renderer/renderToString.ts",
    "content": "import { type App, type VNode, createVNode, isVNode } from \"../runtime-core\";\nimport { isPromise, isString } from \"../shared\";\nimport { type SSRBuffer, type SSRContext, renderComponentVNode } from \"./render\";\n\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // raw vnode, wrap with app\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }, null, null),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // rendering an app\n  const app = input;\n  const vnode = createVNode(app._component, app._props, null);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element\nconst SVG_TAGS =\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,\" +\n  \"defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,\" +\n  \"feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,\" +\n  \"feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,\" +\n  \"feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,\" +\n  \"fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,\" +\n  \"foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,\" +\n  \"mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,\" +\n  \"polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,\" +\n  \"text,textPath,title,tspan,unknown,use,view\";\n\nexport const isHTMLTag = makeMap(HTML_TAGS);\n\nexport const isSVGTag = makeMap(SVG_TAGS);\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/general.ts",
    "content": "export const isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const objectToString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string) => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n\nexport const isOn = (key: string) =>\n  key.charCodeAt(0) === 111 /* o */ &&\n  key.charCodeAt(1) === 110 /* n */ &&\n  (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return (\n    (isObject(val) || isFunction(val)) &&\n    isFunction((val as any).then) &&\n    isFunction((val as any).catch)\n  );\n};\n\nexport const toDisplayString = (val: unknown): string => {\n  return isString(val)\n    ? val\n    : val == null\n      ? \"\"\n      : isArray(val) ||\n          (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))\n        ? JSON.stringify(val, null, 2)\n        : String(val);\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/index.ts",
    "content": "export * from \"./general\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\nexport * from \"./shapeFlags\";\nexport * from \"./patchFlags\";\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \"./general\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/patchFlags.ts",
    "content": "export const enum PatchFlags {\n  // Indicates an element with dynamic textContent\n  TEXT = 1,\n  // Indicates an element with dynamic class binding\n  CLASS = 1 << 1,\n  // Indicates an element with dynamic style\n  STYLE = 1 << 2,\n  // Indicates an element with dynamic props other than class and style\n  PROPS = 1 << 3,\n  // Indicates an element with props that need full diffing\n  FULL_PROPS = 1 << 4,\n  // Indicates an element with dynamic key (for v-if/v-for)\n  NEED_HYDRATION = 1 << 5,\n  // Indicates a fragment whose children order doesn't change\n  STABLE_FRAGMENT = 1 << 6,\n  // Indicates a fragment with keyed or partially keyed children\n  KEYED_FRAGMENT = 1 << 7,\n  // Indicates a fragment with unkeyed children\n  UNKEYED_FRAGMENT = 1 << 8,\n  // Indicates an element that only needs non-props patching\n  NEED_PATCH = 1 << 9,\n  // Indicates a component with dynamic slots\n  DYNAMIC_SLOTS = 1 << 10,\n  // Indicates a fragment was created only because user placed comments at root level\n  DEV_ROOT_FRAGMENT = 1 << 11,\n  // Special flag to indicate that the node is hoisted\n  HOISTED = -1,\n  // Special flag to bail out of optimization\n  BAIL = -2,\n}\n\nexport const PatchFlagNames: Record<number, string> = {\n  [PatchFlags.TEXT]: \"TEXT\",\n  [PatchFlags.CLASS]: \"CLASS\",\n  [PatchFlags.STYLE]: \"STYLE\",\n  [PatchFlags.PROPS]: \"PROPS\",\n  [PatchFlags.FULL_PROPS]: \"FULL_PROPS\",\n  [PatchFlags.NEED_HYDRATION]: \"NEED_HYDRATION\",\n  [PatchFlags.STABLE_FRAGMENT]: \"STABLE_FRAGMENT\",\n  [PatchFlags.KEYED_FRAGMENT]: \"KEYED_FRAGMENT\",\n  [PatchFlags.UNKEYED_FRAGMENT]: \"UNKEYED_FRAGMENT\",\n  [PatchFlags.NEED_PATCH]: \"NEED_PATCH\",\n  [PatchFlags.DYNAMIC_SLOTS]: \"DYNAMIC_SLOTS\",\n  [PatchFlags.DEV_ROOT_FRAGMENT]: \"DEV_ROOT_FRAGMENT\",\n  [PatchFlags.HOISTED]: \"HOISTED\",\n  [PatchFlags.BAIL]: \"BAIL\",\n};\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  STATEFUL_COMPONENT = 1 << 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,\n  COMPONENT_KEPT_ALIVE = 1 << 9,\n}\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/packages/shared/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/tests/e2e.spec.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport {\n  createApp,\n  h,\n  renderToString,\n  KeepAlive,\n  onActivated,\n  onDeactivated,\n  ref,\n  Transition,\n} from \"../packages\";\n\nlet host: HTMLElement;\nconst initHost = () => {\n  host = document.createElement(\"div\");\n  host.setAttribute(\"id\", \"host\");\n  document.body.appendChild(host);\n};\nbeforeEach(() => initHost());\nafterEach(() => host.remove());\n\ndescribe(\"90_web_application_essentials/010_ssr\", () => {\n  it(\"should render to string\", async () => {\n    const app = createApp({\n      template: `<div>Hello SSR!</div>`,\n    });\n\n    const html = await renderToString(app);\n    // Single root element, but still wrapped in fragment comments by the template compiler\n    expect(html).toContain(\"<div>Hello SSR!</div>\");\n  });\n\n  it(\"should escape HTML in text content\", async () => {\n    const app = createApp({\n      template: `<div>{{ text }}</div>`,\n      setup() {\n        return { text: \"<script>alert('xss')</script>\" };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;\");\n  });\n\n  it(\"should render with class and style bindings\", async () => {\n    const app = createApp({\n      template: `<div :class=\"cls\" :style=\"styles\">content</div>`,\n      setup() {\n        return {\n          cls: { active: true, disabled: false },\n          styles: { color: \"red\", fontSize: \"14px\" },\n        };\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain('class=\"active\"');\n    expect(html).toContain('style=\"color:red;font-size:14px;\"');\n  });\n\n  it(\"should render fragment\", async () => {\n    const app = createApp({\n      template: `<p>one</p><p>two</p>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<p>one</p><p>two</p>\");\n  });\n\n  it(\"should render nested components\", async () => {\n    const Child = {\n      props: [\"name\"],\n      template: `<span>{{ name }}</span>`,\n    };\n\n    const app = createApp({\n      components: { Child },\n      template: `<div><Child name=\"test\" /></div>`,\n    });\n\n    const html = await renderToString(app);\n    expect(html).toContain(\"<span>\");\n    expect(html).toContain(\"</span>\");\n    expect(html).toContain(\"<div>\");\n  });\n\n  it(\"should render with h function\", async () => {\n    const app = createApp({\n      render() {\n        return h(\"div\", { class: \"test\" }, \"Hello from h()\");\n      },\n    });\n\n    const html = await renderToString(app);\n    expect(html).toBe('<div class=\"test\">Hello from h()</div>');\n  });\n});\n\ndescribe(\"90_web_application_essentials/020_keep_alive\", () => {\n  it(\"should render KeepAlive wrapper\", () => {\n    const Child = {\n      name: \"Child\",\n      template: `<div>child content</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child content\");\n  });\n\n  it(\"should set COMPONENT_SHOULD_KEEP_ALIVE flag\", () => {\n    const Child = {\n      name: \"Child\",\n      render() {\n        return h(\"div\", null, \"child\");\n      },\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n  });\n\n  it(\"should render with activated/deactivated hooks registered\", () => {\n    let activated = false;\n    let deactivated = false;\n\n    const Child = {\n      name: \"Child\",\n      setup() {\n        onActivated(() => {\n          activated = true;\n        });\n        onDeactivated(() => {\n          deactivated = true;\n        });\n        return {};\n      },\n      template: `<div>child</div>`,\n    };\n\n    const app = createApp({\n      components: { Child, KeepAlive },\n      render() {\n        return h(KeepAlive, null, {\n          default: () => [h(Child)],\n        });\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"child\");\n    // Hooks are registered but not yet called\n    expect(activated).toBe(false);\n    expect(deactivated).toBe(false);\n  });\n});\n\ndescribe(\"90_web_application_essentials/030_transition\", () => {\n  it(\"should render Transition component with child\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => [h(\"div\", { class: \"content\" }, \"Hello Transition\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Hello Transition\");\n    expect(host.innerHTML).toContain(\"content\");\n  });\n\n  it(\"should apply transition hooks to child vnode\", () => {\n    const show = ref(true);\n\n    const app = createApp({\n      setup() {\n        return { show };\n      },\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => (show.value ? [h(\"div\", null, \"visible\")] : []),\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"visible\");\n  });\n\n  it(\"should support custom transition classes\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          {\n            enterFromClass: \"custom-enter-from\",\n            enterActiveClass: \"custom-enter-active\",\n            enterToClass: \"custom-enter-to\",\n            leaveFromClass: \"custom-leave-from\",\n            leaveActiveClass: \"custom-leave-active\",\n            leaveToClass: \"custom-leave-to\",\n          },\n          {\n            default: () => [h(\"div\", null, \"custom classes\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"custom classes\");\n  });\n\n  it(\"should return undefined when no children\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\" },\n          {\n            default: () => [],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    // When no children, renders as comment node\n    expect(host.innerHTML).toBe(\"<!---->\");\n  });\n\n  it(\"should support mode prop\", () => {\n    const app = createApp({\n      render() {\n        return h(\n          Transition,\n          { name: \"fade\", mode: \"out-in\" },\n          {\n            default: () => [h(\"div\", null, \"out-in mode\")],\n          },\n        );\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"out-in mode\");\n  });\n});\n\ndescribe(\"90_web_application_essentials/040_static_hoisting\", () => {\n  it(\"should render static elements correctly\", () => {\n    const app = createApp({\n      template: `<div><span>static</span><p>{{ msg }}</p></div>`,\n      setup() {\n        return { msg: \"dynamic\" };\n      },\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"static\");\n    expect(host.innerHTML).toContain(\"dynamic\");\n  });\n\n  it(\"should render multiple static children\", () => {\n    const app = createApp({\n      template: `<div><span>one</span><span>two</span><span>three</span></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"one\");\n    expect(host.innerHTML).toContain(\"two\");\n    expect(host.innerHTML).toContain(\"three\");\n  });\n\n  it(\"should render mixed static and dynamic content\", () => {\n    const count = ref(0);\n    const app = createApp({\n      setup() {\n        return { count };\n      },\n      template: `<div><header>Static Header</header><main>Count: {{ count }}</main><footer>Static Footer</footer></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Static Header\");\n    expect(host.innerHTML).toContain(\"Count: 0\");\n    expect(host.innerHTML).toContain(\"Static Footer\");\n  });\n\n  it(\"should preserve static attributes\", () => {\n    const app = createApp({\n      template: `<div><span class=\"static-class\" id=\"static-id\">content</span></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain('class=\"static-class\"');\n    expect(host.innerHTML).toContain('id=\"static-id\"');\n  });\n\n  it(\"should handle deeply nested static elements\", () => {\n    const app = createApp({\n      template: `<div><section><article><p>Deep static content</p></article></section></div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"Deep static content\");\n    expect(host.innerHTML).toContain(\"<section>\");\n    expect(host.innerHTML).toContain(\"<article>\");\n  });\n});\n\ndescribe(\"90_web_application_essentials/050_patch_flags\", () => {\n  it(\"should render elements with dynamic text\", () => {\n    const msg = ref(\"hello\");\n    const app = createApp({\n      setup() {\n        return { msg };\n      },\n      template: `<div>{{ msg }}</div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"hello\");\n  });\n\n  it(\"should render elements with dynamic class\", () => {\n    const isActive = ref(true);\n    const app = createApp({\n      setup() {\n        return { isActive };\n      },\n      template: `<div :class=\"{ active: isActive }\">content</div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain('class=\"active\"');\n  });\n\n  it(\"should render elements with dynamic style\", () => {\n    const color = ref(\"red\");\n    const app = createApp({\n      setup() {\n        return { color };\n      },\n      template: `<div :style=\"{ color }\">styled content</div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"color\");\n  });\n\n  it(\"should render elements with dynamic props\", () => {\n    const id = ref(\"my-id\");\n    const app = createApp({\n      setup() {\n        return { id };\n      },\n      template: `<div :id=\"id\">with dynamic id</div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain('id=\"my-id\"');\n  });\n\n  it(\"should render elements with multiple dynamic bindings\", () => {\n    const app = createApp({\n      setup() {\n        return {\n          cls: \"dynamic-class\",\n          style: { color: \"blue\" },\n          title: \"dynamic-title\",\n        };\n      },\n      template: `<div :class=\"cls\" :style=\"style\" :title=\"title\">multi-dynamic</div>`,\n    });\n\n    app.mount(\"#host\");\n    expect(host.innerHTML).toContain(\"dynamic-class\");\n    expect(host.innerHTML).toContain(\"dynamic-title\");\n    expect(host.innerHTML).toContain(\"multi-dynamic\");\n  });\n});\n"
  },
  {
    "path": "book/impls/90_web_application_essentials/050_patch_flags/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/src/App.vue",
    "content": "<script>\nimport { reactive } from 'hyper-ultimate-super-extreme-minimal-vue'\n\nexport default {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"increment\">state: {{ state.count }}</button>\n</template>\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/src/main.ts",
    "content": "import { createApp } from \"hyper-ultimate-super-extreme-minimal-vue\";\n\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"hyper-ultimate-super-extreme-minimal-vue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/examples/playground/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nimport { VitePluginChibivue } from \"../../packages\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\nexport default defineConfig({\n  resolve: {\n    alias: {\n      \"hyper-ultimate-super-extreme-minimal-vue\": path.resolve(dirname, \"../../packages\"),\n    },\n  },\n  plugins: [VitePluginChibivue()],\n});\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/package.json",
    "content": "{\n  \"name\": \"chibivue\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.9\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/packages/index.ts",
    "content": "// createApp API\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>;\n  render: (ctx: Record<string, unknown>) => VNode;\n};\nlet update: (() => void) | null = null;\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!;\n    let prevVNode: VNode | null = null;\n    const setupState = option.setup();\n    update = () => {\n      const vnode = option.render(setupState);\n      render(prevVNode, vnode, container);\n      prevVNode = vnode;\n    };\n    update();\n  },\n});\n\n// Virtual DOM patch\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag);\n    el.textContent = vnode.children;\n    el.addEventListener(\"click\", vnode.onClick);\n    container.appendChild(el);\n  };\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    (container.firstElementChild as Element).textContent = n2.children;\n  };\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2);\n};\n\n// Virtual DOM\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string };\nexport const h = (tag: string, onClick: (e: Event) => void, children: string): VNode => ({\n  tag,\n  onClick,\n  children,\n});\n\n// Reactivity System\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver);\n      update?.();\n      return res;\n    },\n  });\n\n// Template Compiler\ntype AST = {\n  tag: string;\n  onClick: string;\n  children: (string | Interpolation)[];\n};\ntype Interpolation = { content: string };\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/;\n  const [_, tag, onClick, children] = template.match(RE) || [];\n  if (!tag || !onClick || !children) throw new Error(\"Invalid template!\");\n  const regex = /{{(.*?)}}/g;\n  let match: RegExpExecArray | null;\n  let lastIndex = 0;\n  const parsedChildren: AST[\"children\"] = [];\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index && parsedChildren.push(children.substring(lastIndex, match.index));\n    parsedChildren.push({ content: match[1].trim() });\n    lastIndex = match.index + match[0].length;\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex));\n  return { tag, onClick, children: parsedChildren };\n};\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map((child) => (typeof child === \"object\" ? `\\$\\{_ctx.${child.content}\\}` : child))\n    .join(\"\")}\\`)`;\nconst compile = (template: string): string => codegen(parse(template));\n\n// SFC Compiler (Vite plugin)\nexport const VitePluginChibivue = () => ({\n  name: \"vite-plugin-chibivue\",\n  transform: (code: string, id: string) => (id.endsWith(\".vue\") ? compileSFC(code) : null),\n});\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] = sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? [];\n  const [___, defaultExported] = scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? [];\n  const [__, templateContent] = sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? [];\n  if (!scriptContent || !defaultExported || !templateContent) throw new Error(\"Invalid SFC!\");\n  let code = \"\";\n  code += \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\";\n  code += `const options = ${defaultExported}\\n`;\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`;\n  code += \"export default options;\\n\";\n  return { code };\n};\n"
  },
  {
    "path": "book/impls/bonus/hyper-ultimate-super-extreme-minimal-vue/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"paths\": {\n      \"hyper-ultimate-super-extreme-minimal-vue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "book/online-book/.vitepress/config/en.ts",
    "content": "import type { DefaultTheme, LocaleSpecificConfig } from \"vitepress\";\n\nexport const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {\n  themeConfig: {\n    nav: [\n      { text: \"Home\", link: \"/\" },\n      { text: \"Start Learning\", link: \"/00-introduction/010-about\" },\n    ],\n    sidebar: [\n      {\n        text: \"Getting Started\",\n        collapsed: false,\n        items: [\n          { text: \"Getting Started\", link: \"/00-introduction/010-about\" },\n          {\n            text: \"What is Vue.js?\",\n            link: \"/00-introduction/020-what-is-vue\",\n          },\n          {\n            text: \"Key Elements of Vue.js\",\n            link: \"/00-introduction/030-vue-core-components\",\n          },\n          {\n            text: \"Approach in This Book and Setting Up the Environment\",\n            link: \"/00-introduction/040-setup-project\",\n          },\n        ],\n      },\n      {\n        text: \"Minimum Example\",\n        collapsed: false,\n        items: [\n          {\n            text: \"First Rendering and the createApp API\",\n            link: \"/10-minimum-example/010-create-app-api\",\n          },\n          {\n            text: \"Package Architecture\",\n            link: \"/10-minimum-example/015-package-architecture\",\n          },\n          {\n            text: \"Let's Enable Rendering HTML Elements\",\n            link: \"/10-minimum-example/020-simple-h-function\",\n          },\n          {\n            text: \"Let's work on supporting event handlers and attributes.\",\n            link: \"/10-minimum-example/025-event-handler-and-attrs\",\n          },\n          {\n            text: \"Prerequisite Knowledge for the Reactivity System\",\n            link: \"/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system\",\n          },\n          {\n            text: \"Try Implementing a Small Reactivity System\",\n            link: \"/10-minimum-example/035-try-implementing-a-minimum-reactivity-system\",\n          },\n          {\n            text: \"A Minimal Virtual DOM\",\n            link: \"/10-minimum-example/040-minimum-virtual-dom\",\n          },\n          {\n            text: \"Aspiring for Component-Oriented Development\",\n            link: \"/10-minimum-example/050-minimum-component\",\n          },\n          {\n            text: \"Component Props\",\n            link: \"/10-minimum-example/051-component-props\",\n          },\n          {\n            text: \"Component Emit\",\n            link: \"/10-minimum-example/052-component-emits\",\n          },\n          {\n            text: \"Understanding the Template Compiler\",\n            link: \"/10-minimum-example/060-template-compiler\",\n          },\n          {\n            text: \"Implementing the Template Compiler\",\n            link: \"/10-minimum-example/061-template-compiler-impl\",\n          },\n          {\n            text: \"Desire to Write More Complex HTML\",\n            link: \"/10-minimum-example/070-more-complex-parser\",\n          },\n          {\n            text: \"Data Binding\",\n            link: \"/10-minimum-example/080-template-binding\",\n          },\n          {\n            text: \"Developing with SFC (Peripheral Knowledge)\",\n            link: \"/10-minimum-example/090-prerequisite-knowledge-for-the-sfc\",\n          },\n          {\n            text: \"Parse SFC\",\n            link: \"/10-minimum-example/091-parse-sfc\",\n          },\n          {\n            text: \"SFC template block\",\n            link: \"/10-minimum-example/092-compile-sfc-template\",\n          },\n          {\n            text: \"SFC script block \",\n            link: \"/10-minimum-example/093-compile-sfc-script\",\n          },\n          {\n            text: \"SFC style block\",\n            link: \"/10-minimum-example/094-compile-sfc-style\",\n          },\n          {\n            text: \"Taking a Short Break\",\n            link: \"/10-minimum-example/100-break\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Virtual DOM\",\n        collapsed: false,\n        items: [\n          {\n            text: \"key Attribute and Patch Rendering\",\n            link: \"/20-basic-virtual-dom/010-patch-keyed-children\",\n          },\n          {\n            text: \"Bit-Level Representation of VNodes\",\n            link: \"/20-basic-virtual-dom/020-bit-flags\",\n          },\n          {\n            text: \"Scheduler\",\n            link: \"/20-basic-virtual-dom/030-scheduler\",\n          },\n          {\n            text: \"Patch for Unhandled Props\",\n            link: \"/20-basic-virtual-dom/040-patch-other-attrs\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Reactivity System\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Reactivity Optimization\",\n            link: \"/30-basic-reactivity-system/005-reactivity-optimization\",\n          },\n          {\n            text: \"ref API\",\n            link: \"/30-basic-reactivity-system/010-ref-api\",\n          },\n          {\n            text: \"computed / watch API\",\n            link: \"/30-basic-reactivity-system/020-computed-watch\",\n          },\n          {\n            text: \"Various Reactive Proxy Handlers\",\n            link: \"/30-basic-reactivity-system/030-reactive-proxy-handlers\",\n          },\n          {\n            text: \"Effect Cleanup and Effect Scope\",\n            link: \"/30-basic-reactivity-system/040-effect-scope\",\n          },\n          {\n            text: \"Other Reactivity APIs\",\n            link: \"/30-basic-reactivity-system/050-other-apis\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Component System\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Lifecycle Hooks\",\n            link: \"/40-basic-component-system/010-lifecycle-hooks\",\n          },\n          {\n            text: \"Provide/Inject\",\n            link: \"/40-basic-component-system/020-provide-inject\",\n          },\n          {\n            text: \"Component Proxies and setupContext\",\n            link: \"/40-basic-component-system/030-component-proxy-setup-context\",\n          },\n          {\n            text: \"Slots\",\n            link: \"/40-basic-component-system/040-component-slot\",\n          },\n          {\n            text: \"Supporting Options API\",\n            link: \"/40-basic-component-system/050-options-api\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Template Compiler\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Refactoring Implementation of Transformer for Codegen\",\n            link: \"/50-basic-template-compiler/010-transform\",\n          },\n          {\n            text: \"Implementing Directives (v-bind)\",\n            link: \"/50-basic-template-compiler/020-v-bind\",\n          },\n          {\n            text: \"Eval expression in template\",\n            link: \"/50-basic-template-compiler/022-transform-expression\",\n          },\n          {\n            text: \"Supporting v-on\",\n            link: \"/50-basic-template-compiler/025-v-on\",\n          },\n          {\n            text: \"compiler-dom and Event Modifiers\",\n            link: \"/50-basic-template-compiler/027-event-modifier\",\n          },\n          {\n            text: \"Support for Fragment\",\n            link: \"/50-basic-template-compiler/030-fragment\",\n          },\n          {\n            text: \"Support for Comment Node\",\n            link: \"/50-basic-template-compiler/035-comment\",\n          },\n          {\n            text: \"v-if and Structural Directives\",\n            link: \"/50-basic-template-compiler/040-v-if-and-structural-directive\",\n          },\n          {\n            text: \"Support for v-for\",\n            link: \"/50-basic-template-compiler/050-v-for\",\n          },\n          {\n            text: \"Resolving Components\",\n            link: \"/50-basic-template-compiler/070-resolve-component\",\n          },\n          {\n            text: \"Support for Slots (Definition)\",\n            link: \"/50-basic-template-compiler/080-component-slot-outlet\",\n          },\n          {\n            text: \"Support for Slots (Usage)\",\n            link: \"/50-basic-template-compiler/085-component-slot-insert\",\n          },\n          {\n            text: \"Other Directives\",\n            link: \"/50-basic-template-compiler/090-other-directives\",\n          },\n          {\n            text: \"Compiler Refinements\",\n            link: \"/50-basic-template-compiler/100-chore-compiler\",\n          },\n          {\n            text: \"Parser Optimization\",\n            link: \"/50-basic-template-compiler/110-parser-optimization\",\n          },\n          {\n            text: \"Custom Directives\",\n            link: \"/50-basic-template-compiler/500-custom-directive\",\n          },\n        ],\n      },\n      {\n        text: \"Basic SFC Compiler\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Supporting script setup\",\n            link: \"/60-basic-sfc-compiler/010-script-setup\",\n          },\n          {\n            text: \"Supporting defineProps\",\n            link: \"/60-basic-sfc-compiler/020-define-props\",\n          },\n          {\n            text: \"Supporting defineEmits\",\n            link: \"/60-basic-sfc-compiler/030-define-emits\",\n          },\n          {\n            text: \"Supporting Scoped CSS\",\n            link: \"/60-basic-sfc-compiler/040-scoped-css\",\n          },\n          {\n            text: \"Supporting Props Destructure\",\n            link: \"/60-basic-sfc-compiler/050-props-destructure\",\n          },\n          {\n            text: \"Type-based defineProps/defineEmits\",\n            link: \"/60-basic-sfc-compiler/060-type-based-macros\",\n          },\n        ],\n      },\n      {\n        text: \"Web Application Essentials\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Plugins\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Router\",\n                link: \"/90-web-application-essentials/010-plugins/010-router\",\n              },\n              {\n                text: \"CSS Preprocessors\",\n                link: \"/90-web-application-essentials/010-plugins/020-preprocessors\",\n              },\n              {\n                text: \"Store\",\n                link: \"/90-web-application-essentials/010-plugins/020-store\",\n              },\n              {\n                text: \"Data Fetch\",\n                link: \"/90-web-application-essentials/010-plugins/030-data-fetch\",\n              },\n              {\n                text: \"Language Tools\",\n                link: \"/90-web-application-essentials/010-plugins/040-language-tools\",\n              },\n            ],\n          },\n          {\n            text: \"Server Side Rendering\",\n            collapsed: false,\n            items: [\n              {\n                text: \"renderToString\",\n                link: \"/90-web-application-essentials/020-ssr/010-create-ssr-app\",\n              },\n              {\n                text: \"Hydration\",\n                link: \"/90-web-application-essentials/020-ssr/020-hydration\",\n              },\n              {\n                text: \"Compiler SSR\",\n                link: \"/90-web-application-essentials/020-ssr/030-compiler-ssr\",\n              },\n            ],\n          },\n          {\n            text: \"Built-in Components\",\n            collapsed: false,\n            items: [\n              {\n                text: \"KeepAlive\",\n                link: \"/90-web-application-essentials/030-builtins/010-keep-alive\",\n              },\n              {\n                text: \"Transition\",\n                link: \"/90-web-application-essentials/030-builtins/030-transition\",\n              },\n            ],\n          },\n          {\n            text: \"Optimizations\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Static Hoisting\",\n                link: \"/90-web-application-essentials/040-optimizations/010-static-hoisting\",\n              },\n              {\n                text: \"Patch Flags\",\n                link: \"/90-web-application-essentials/040-optimizations/020-patch-flags\",\n              },\n              {\n                text: \"Tree Flattening\",\n                link: \"/90-web-application-essentials/040-optimizations/030-tree-flattening\",\n              },\n            ],\n          },\n          {\n            text: \"Vapor Mode\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Vapor Mode\",\n                link: \"/90-web-application-essentials/050-vapor/010-introduction\",\n              },\n              {\n                text: \"Vapor Compiler\",\n                link: \"/90-web-application-essentials/050-vapor/020-vapor-compiler\",\n              },\n              {\n                text: \"Vapor SSR\",\n                link: \"/90-web-application-essentials/050-vapor/030-vapor-ssr\",\n              },\n            ],\n          },\n        ],\n      },\n      {\n        text: \"Appendix\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Writing Vue.js in 15 minutes.\",\n            collapsed: false,\n            items: [\n              {\n                text: \"chibivue, isn't it small...?\",\n                link: \"/bonus/hyper-ultimate-super-extreme-minimal-vue/\",\n              },\n              {\n                text: \"Implement\",\n                link: \"/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl\",\n              },\n            ],\n          },\n          {\n            text: \"debug original Vue.js source\",\n            link: \"/bonus/debug-vuejs-core\",\n          },\n        ],\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "book/online-book/.vitepress/config/index.ts",
    "content": "import { sharedConfig } from \"./shared.js\";\nimport { jaConfig } from \"./ja\";\nimport { enConfig } from \"./en.js\";\nimport { zhCnConfig } from \"./zh-cn.js\";\nimport { zhTwConfig } from \"./zh-tw.js\";\nimport { defineConfig } from \"vitepress\";\n\nexport default defineConfig({\n  ...sharedConfig,\n  locales: {\n    root: { label: \"English\", lang: \"en\", link: \"/\", ...enConfig },\n    ja: { label: \"日本語\", lang: \"ja\", link: \"/ja\", ...jaConfig },\n    \"zh-cn\": {\n      label: \"简体中文\",\n      lang: \"zh-CN\",\n      link: \"/zh-cn\",\n      ...zhCnConfig,\n    },\n    \"zh-tw\": {\n      label: \"繁體中文\",\n      lang: \"zh-TW\",\n      link: \"/zh-tw\",\n      ...zhTwConfig,\n    },\n  },\n});\n"
  },
  {
    "path": "book/online-book/.vitepress/config/ja.ts",
    "content": "import type { DefaultTheme, LocaleSpecificConfig } from \"vitepress\";\n\nexport const jaConfig: LocaleSpecificConfig<DefaultTheme.Config> = {\n  themeConfig: {\n    nav: [\n      { text: \"Home\", link: \"/ja/\" },\n      { text: \"Start Learning\", link: \"/ja/00-introduction/010-about\" },\n    ],\n    sidebar: [\n      {\n        text: \"Getting Started\",\n        collapsed: false,\n        items: [\n          { text: \"初めに\", link: \"/ja/00-introduction/010-about\" },\n          { text: \"Vue.jsとは\", link: \"/ja/00-introduction/020-what-is-vue\" },\n          {\n            text: \"Vue.jsを構成する主要な要素\",\n            link: \"/ja/00-introduction/030-vue-core-components\",\n          },\n          {\n            text: \"本書の進め方と環境構築\",\n            link: \"/ja/00-introduction/040-setup-project\",\n          },\n        ],\n      },\n      {\n        text: \"Minimum Example\",\n        collapsed: false,\n        items: [\n          {\n            text: \"初めてのレンダリングと createApp API\",\n            link: \"/ja/10-minimum-example/010-create-app-api\",\n          },\n          {\n            text: \"パッケージの設計\",\n            link: \"/ja/10-minimum-example/015-package-architecture\",\n          },\n          {\n            text: \"HTML要素をレンダリングできるようにしよう\",\n            link: \"/ja/10-minimum-example/020-simple-h-function\",\n          },\n          {\n            text: \"イベントハンドラや属性に対応してみる\",\n            link: \"/ja/10-minimum-example/025-event-handler-and-attrs\",\n          },\n          {\n            text: \"リアクティビティシステムの前程知識\",\n            link: \"/ja/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system\",\n          },\n          {\n            text: \"小さいリアクティビティシステムを実装してみる\",\n            link: \"/ja/10-minimum-example/035-try-implementing-a-minimum-reactivity-system\",\n          },\n          {\n            text: \"小さい仮想 DOM\",\n            link: \"/ja/10-minimum-example/040-minimum-virtual-dom\",\n          },\n          {\n            text: \"コンポーネント指向で開発したい\",\n            link: \"/ja/10-minimum-example/050-minimum-component\",\n          },\n          {\n            text: \"Props の実装\",\n            link: \"/ja/10-minimum-example/051-component-props\",\n          },\n          {\n            text: \"Emit の実装\",\n            link: \"/ja/10-minimum-example/052-component-emits\",\n          },\n          {\n            text: \"テンプレートコンパイラを理解する\",\n            link: \"/ja/10-minimum-example/060-template-compiler\",\n          },\n          {\n            text: \"テンプレートコンパイラを実装する\",\n            link: \"/ja/10-minimum-example/061-template-compiler-impl\",\n          },\n          {\n            text: \"もっと複雑な HTML を書きたい\",\n            link: \"/ja/10-minimum-example/070-more-complex-parser\",\n          },\n          {\n            text: \"データバインディング\",\n            link: \"/ja/10-minimum-example/080-template-binding\",\n          },\n          {\n            text: \"SFC で開発したい (周辺知識編)\",\n            link: \"/ja/10-minimum-example/090-prerequisite-knowledge-for-the-sfc\",\n          },\n          {\n            text: \"SFC のパース\",\n            link: \"/ja/10-minimum-example/091-parse-sfc\",\n          },\n          {\n            text: \"SFC の template block\",\n            link: \"/ja/10-minimum-example/092-compile-sfc-template\",\n          },\n          {\n            text: \"SFC の script block\",\n            link: \"/ja/10-minimum-example/093-compile-sfc-script\",\n          },\n          {\n            text: \"SFC の style block\",\n            link: \"/ja/10-minimum-example/094-compile-sfc-style\",\n          },\n          {\n            text: \"ちょっと一息\",\n            link: \"/ja/10-minimum-example/100-break\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Virtual DOM\",\n        collapsed: false,\n        items: [\n          {\n            text: \"key属性とパッチレンダリング\",\n            link: \"/ja/20-basic-virtual-dom/010-patch-keyed-children\",\n          },\n          {\n            text: \"ビットによるVNodeの表現\",\n            link: \"/ja/20-basic-virtual-dom/020-bit-flags\",\n          },\n          {\n            text: \"スケジューラ\",\n            link: \"/ja/20-basic-virtual-dom/030-scheduler\",\n          },\n          {\n            text: \"対応できていない Props のパッチ\",\n            link: \"/ja/20-basic-virtual-dom/040-patch-other-attrs\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Reactivity System\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Reactivity の最適化\",\n            link: \"/ja/30-basic-reactivity-system/005-reactivity-optimization\",\n          },\n          {\n            text: \"ref api\",\n            link: \"/ja/30-basic-reactivity-system/010-ref-api\",\n          },\n          {\n            text: \"computed / watch api\",\n            link: \"/ja/30-basic-reactivity-system/020-computed-watch\",\n          },\n          {\n            text: \"様々な Reactive Proxy Handler\",\n            link: \"/ja/30-basic-reactivity-system/030-reactive-proxy-handlers\",\n          },\n          {\n            text: \"Effect のクリーンアップと Effect Scope\",\n            link: \"/ja/30-basic-reactivity-system/040-effect-scope\",\n          },\n          {\n            text: \"その他の reactivity api\",\n            link: \"/ja/30-basic-reactivity-system/050-other-apis\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Component System\",\n        collapsed: false,\n        items: [\n          {\n            text: \"ライフサイクルフック\",\n            link: \"/ja/40-basic-component-system/010-lifecycle-hooks\",\n          },\n          {\n            text: \"Provide/Inject\",\n            link: \"/ja/40-basic-component-system/020-provide-inject\",\n          },\n          {\n            text: \"コンポーネントの Proxy と setupContext\",\n            link: \"/ja/40-basic-component-system/030-component-proxy-setup-context\",\n          },\n          {\n            text: \"スロット\",\n            link: \"/ja/40-basic-component-system/040-component-slot\",\n          },\n          {\n            text: \"Options APIに対応する\",\n            link: \"/ja/40-basic-component-system/050-options-api\",\n          },\n        ],\n      },\n      {\n        text: \"Basic Template Compiler\",\n        collapsed: false,\n        items: [\n          {\n            text: \"Transformer の実装 の Codegen のリファクタ\",\n            link: \"/ja/50-basic-template-compiler/010-transform\",\n          },\n          {\n            text: \"ディレクティブを実装しよう (v-bind)\",\n            link: \"/ja/50-basic-template-compiler/020-v-bind\",\n          },\n          {\n            text: \"template 内での式の評価\",\n            link: \"/ja/50-basic-template-compiler/022-transform-expression\",\n          },\n          {\n            text: \"v-on に対応する\",\n            link: \"/ja/50-basic-template-compiler/025-v-on\",\n          },\n          {\n            text: \"compiler-dom とイベント修飾子\",\n            link: \"/ja/50-basic-template-compiler/027-event-modifier\",\n          },\n          {\n            text: \"Fragment に対応する\",\n            link: \"/ja/50-basic-template-compiler/030-fragment\",\n          },\n          {\n            text: \"コメントアウトに対応する\",\n            link: \"/ja/50-basic-template-compiler/035-comment\",\n          },\n          {\n            text: \"v-if と構造的ディレクティブ\",\n            link: \"/ja/50-basic-template-compiler/040-v-if-and-structural-directive\",\n          },\n          {\n            text: \"v-for に対応する\",\n            link: \"/ja/50-basic-template-compiler/050-v-for\",\n          },\n          {\n            text: \"コンポーネントを解決する\",\n            link: \"/ja/50-basic-template-compiler/070-resolve-component\",\n          },\n          {\n            text: \"スロットに対応する (定義編)\",\n            link: \"/ja/50-basic-template-compiler/080-component-slot-outlet\",\n          },\n          {\n            text: \"スロットに対応する (利用編)\",\n            link: \"/ja/50-basic-template-compiler/085-component-slot-insert\",\n          },\n          {\n            text: \"その他のディレクティブ\",\n            link: \"/ja/50-basic-template-compiler/090-other-directives\",\n          },\n          {\n            text: \"コンパイラの細かい調整\",\n            link: \"/ja/50-basic-template-compiler/100-chore-compiler\",\n          },\n          {\n            text: \"パーサーの最適化\",\n            link: \"/ja/50-basic-template-compiler/110-parser-optimization\",\n          },\n          {\n            text: \"カスタムディレクティブ\",\n            link: \"/ja/50-basic-template-compiler/500-custom-directive\",\n          },\n        ],\n      },\n      {\n        text: \"Basic SFC Compiler\",\n        collapsed: false,\n        items: [\n          {\n            text: \"script setup に対応する\",\n            link: \"/ja/60-basic-sfc-compiler/010-script-setup\",\n          },\n          {\n            text: \"defineProps に対応する\",\n            link: \"/ja/60-basic-sfc-compiler/020-define-props\",\n          },\n          {\n            text: \"defineEmits に対応する\",\n            link: \"/ja/60-basic-sfc-compiler/030-define-emits\",\n          },\n          {\n            text: \"Scoped CSS に対応する\",\n            link: \"/ja/60-basic-sfc-compiler/040-scoped-css\",\n          },\n          {\n            text: \"Props の分割代入に対応する\",\n            link: \"/ja/60-basic-sfc-compiler/050-props-destructure\",\n          },\n          {\n            text: \"型ベースの defineProps/defineEmits\",\n            link: \"/ja/60-basic-sfc-compiler/060-type-based-macros\",\n          },\n        ],\n      },\n      {\n        text: \"Web Application Essentials\",\n        collapsed: false,\n        items: [\n          {\n            text: \"プラグイン\",\n            collapsed: false,\n            items: [\n              {\n                text: \"ルーター\",\n                link: \"/ja/90-web-application-essentials/010-plugins/010-router\",\n              },\n              {\n                text: \"CSS プリプロセッサ\",\n                link: \"/ja/90-web-application-essentials/010-plugins/020-preprocessors\",\n              },\n              {\n                text: \"ストア\",\n                link: \"/ja/90-web-application-essentials/010-plugins/020-store\",\n              },\n              {\n                text: \"データフェッチ\",\n                link: \"/ja/90-web-application-essentials/010-plugins/030-data-fetch\",\n              },\n              {\n                text: \"Language Tools\",\n                link: \"/ja/90-web-application-essentials/010-plugins/040-language-tools\",\n              },\n            ],\n          },\n          {\n            text: \"Server Side Rendering\",\n            collapsed: false,\n            items: [\n              {\n                text: \"renderToString\",\n                link: \"/ja/90-web-application-essentials/020-ssr/010-create-ssr-app\",\n              },\n              {\n                text: \"Hydration\",\n                link: \"/ja/90-web-application-essentials/020-ssr/020-hydration\",\n              },\n              {\n                text: \"Compiler SSR\",\n                link: \"/ja/90-web-application-essentials/020-ssr/030-compiler-ssr\",\n              },\n            ],\n          },\n          {\n            text: \"組み込みコンポーネント\",\n            collapsed: false,\n            items: [\n              {\n                text: \"KeepAlive\",\n                link: \"/ja/90-web-application-essentials/030-builtins/010-keep-alive\",\n              },\n              {\n                text: \"Transition\",\n                link: \"/ja/90-web-application-essentials/030-builtins/030-transition\",\n              },\n            ],\n          },\n          {\n            text: \"最適化\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Static Hoisting\",\n                link: \"/ja/90-web-application-essentials/040-optimizations/010-static-hoisting\",\n              },\n              {\n                text: \"Patch Flags\",\n                link: \"/ja/90-web-application-essentials/040-optimizations/020-patch-flags\",\n              },\n              {\n                text: \"Tree Flattening\",\n                link: \"/ja/90-web-application-essentials/040-optimizations/030-tree-flattening\",\n              },\n            ],\n          },\n          {\n            text: \"Vapor Mode\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Vapor Mode\",\n                link: \"/ja/90-web-application-essentials/050-vapor/010-introduction\",\n              },\n              {\n                text: \"Vapor Compiler\",\n                link: \"/ja/90-web-application-essentials/050-vapor/020-vapor-compiler\",\n              },\n              {\n                text: \"Vapor SSR\",\n                link: \"/ja/90-web-application-essentials/050-vapor/030-vapor-ssr\",\n              },\n            ],\n          },\n        ],\n      },\n      {\n        text: \"付録\",\n        collapsed: false,\n        items: [\n          {\n            text: \"15 分で Vue を作る\",\n            collapsed: false,\n            items: [\n              {\n                text: \"chibivue、デカくないですか...?\",\n                link: \"/ja/bonus/hyper-ultimate-super-extreme-minimal-vue/\",\n              },\n              {\n                text: \"実装\",\n                link: \"/ja/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl\",\n              },\n            ],\n          },\n          {\n            text: \"本家のソースコードをデバッグする\",\n            link: \"/ja/bonus/debug-vuejs-core\",\n          },\n        ],\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "book/online-book/.vitepress/config/shared.ts",
    "content": "import { defineConfig } from \"vitepress\";\nimport Icons from \"unplugin-icons/vite\";\nimport chibivueThemeDark from \"../theme/chibivue-theme.json\";\nimport chibivueThemeLight from \"../theme/chibivue-theme-light.json\";\nimport pkg from \"../../../../package.json\";\n\nexport const sharedConfig = defineConfig({\n  vite: {\n    plugins: [Icons({ compiler: \"vue3\" })],\n    define: {\n      __CHIBIVUE_VERSION__: JSON.stringify(pkg.version),\n    },\n  },\n  title: \"The chibivue Book\",\n  appearance: \"dark\",\n  description: 'Writing Vue.js: Step by Step, from just one line of \"Hello, World\".',\n  lang: \"en\",\n  srcDir: \"src\",\n  srcExclude: [\"__wip\"],\n  markdown: {\n    theme: {\n      light: chibivueThemeLight as any,\n      dark: chibivueThemeDark as any,\n    },\n  },\n  head: [\n    [\n      \"link\",\n      {\n        rel: \"icon\",\n        href: \"/figures/_brand/logo.png\",\n      },\n    ],\n\n    // og\n    [\"meta\", { property: \"og:site_name\", content: \"chibivue\" }],\n    [\"meta\", { property: \"og:url\", content: \"https://book.chibivue.land\" }],\n    [\"meta\", { property: \"og:title\", content: \"chibivue\" }],\n    [\n      \"meta\",\n      {\n        property: \"og:description\",\n        content: 'Writing Vue.js: Step by Step, from just one line of \"Hello, World\".',\n      },\n    ],\n    [\n      \"meta\",\n      {\n        property: \"og:image\",\n        content: \"https://book.chibivue.land/og.png\",\n      },\n    ],\n    [\"meta\", { property: \"og:image:alt\", content: \"chibivue\" }],\n    [\"meta\", { name: \"twitter:site\", content: \"chibivue\" }],\n    [\"meta\", { name: \"twitter:card\", content: \"summary_large_image\" }],\n    [\"meta\", { name: \"twitter:title\", content: \"chibivue\" }],\n    [\n      \"meta\",\n      {\n        name: \"twitter:description\",\n        content: 'Writing Vue.js: Step by Step, from just one line of \"Hello, World\".',\n      },\n    ],\n    [\n      \"meta\",\n      {\n        name: \"twitter:image\",\n        content: \"https://book.chibivue.land/og.png\",\n      },\n    ],\n    [\"meta\", { name: \"twitter:image:alt\", content: \"chibivue\" }],\n  ],\n  themeConfig: {\n    logo: \"/figures/_brand/logo.png\",\n    search: { provider: \"local\" },\n    outline: \"deep\",\n    i18nRouting: true,\n    socialLinks: [\n      {\n        icon: {\n          svg: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\"><image href=\"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko.png\" width=\"20\" height=\"20\"/></svg>',\n        },\n        link: \"https://chibivue.land\",\n        ariaLabel: \"chibivue.land\",\n      },\n      { icon: \"github\", link: \"https://github.com/chibivue-land/chibivue\" },\n      { icon: \"twitter\", link: \"https://twitter.com/ubugeeei\" },\n      { icon: \"discord\", link: \"https://discord.gg/aVHvmbmSRy\" },\n      {\n        icon: {\n          svg: '<svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-heart-fill icon-sponsoring mr-1 v-align-middle color-fg-sponsors\"><path d=\"M7.655 14.916v-.001h-.002l-.006-.003-.018-.01a22.066 22.066 0 0 1-3.744-2.584C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.044 5.231-3.886 6.818a22.094 22.094 0 0 1-3.433 2.414 7.152 7.152 0 0 1-.31.17l-.018.01-.008.004a.75.75 0 0 1-.69 0Z\"></path></svg>',\n        },\n        link: \"https://github.com/sponsors/ubugeeei\",\n      },\n    ],\n    editLink: {\n      pattern: \"https://github.com/chibivue-land/chibivue/blob/main/book/online-book/src/:path\",\n      text: \"Suggest changes to this page\",\n    },\n    footer: {\n      copyright: `Copyright © 2023-${new Date().getFullYear()} ubugeeei`,\n      message: \"Released under the MIT License.\",\n    },\n  },\n});\n"
  },
  {
    "path": "book/online-book/.vitepress/config/zh-cn.ts",
    "content": "import type { DefaultTheme, LocaleSpecificConfig } from \"vitepress\";\n\nexport const zhCnConfig: LocaleSpecificConfig<DefaultTheme.Config> = {\n  themeConfig: {\n    nav: [\n      { text: \"首页\", link: \"/zh-cn/\" },\n      { text: \"开始学习\", link: \"/zh-cn/00-introduction/010-about\" },\n    ],\n    sidebar: [\n      {\n        text: \"入门指南\",\n        collapsed: false,\n        items: [\n          { text: \"入门指南\", link: \"/zh-cn/00-introduction/010-about\" },\n          {\n            text: \"什么是 Vue.js？\",\n            link: \"/zh-cn/00-introduction/020-what-is-vue\",\n          },\n          {\n            text: \"Vue.js 的关键要素\",\n            link: \"/zh-cn/00-introduction/030-vue-core-components\",\n          },\n          {\n            text: \"本书的方法和环境设置\",\n            link: \"/zh-cn/00-introduction/040-setup-project\",\n          },\n        ],\n      },\n      {\n        text: \"最小示例\",\n        collapsed: false,\n        items: [\n          {\n            text: \"第一次渲染和 createApp API\",\n            link: \"/zh-cn/10-minimum-example/010-create-app-api\",\n          },\n          {\n            text: \"包架构\",\n            link: \"/zh-cn/10-minimum-example/015-package-architecture\",\n          },\n          {\n            text: \"让我们启用 HTML 元素渲染\",\n            link: \"/zh-cn/10-minimum-example/020-simple-h-function\",\n          },\n          {\n            text: \"让我们支持事件处理器和属性\",\n            link: \"/zh-cn/10-minimum-example/025-event-handler-and-attrs\",\n          },\n          {\n            text: \"响应式系统的前置知识\",\n            link: \"/zh-cn/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system\",\n          },\n          {\n            text: \"尝试实现一个小型响应式系统\",\n            link: \"/zh-cn/10-minimum-example/035-try-implementing-a-minimum-reactivity-system\",\n          },\n          {\n            text: \"最小虚拟 DOM\",\n            link: \"/zh-cn/10-minimum-example/040-minimum-virtual-dom\",\n          },\n          {\n            text: \"追求组件导向开发\",\n            link: \"/zh-cn/10-minimum-example/050-minimum-component\",\n          },\n          {\n            text: \"组件 Props\",\n            link: \"/zh-cn/10-minimum-example/051-component-props\",\n          },\n          {\n            text: \"组件 Emit\",\n            link: \"/zh-cn/10-minimum-example/052-component-emits\",\n          },\n          {\n            text: \"理解模板编译器\",\n            link: \"/zh-cn/10-minimum-example/060-template-compiler\",\n          },\n          {\n            text: \"实现模板编译器\",\n            link: \"/zh-cn/10-minimum-example/061-template-compiler-impl\",\n          },\n          {\n            text: \"希望编写更复杂的 HTML\",\n            link: \"/zh-cn/10-minimum-example/070-more-complex-parser\",\n          },\n          {\n            text: \"数据绑定\",\n            link: \"/zh-cn/10-minimum-example/080-template-binding\",\n          },\n          {\n            text: \"使用 SFC 开发（外围知识）\",\n            link: \"/zh-cn/10-minimum-example/090-prerequisite-knowledge-for-the-sfc\",\n          },\n          {\n            text: \"解析 SFC\",\n            link: \"/zh-cn/10-minimum-example/091-parse-sfc\",\n          },\n          {\n            text: \"SFC template 块\",\n            link: \"/zh-cn/10-minimum-example/092-compile-sfc-template\",\n          },\n          {\n            text: \"SFC script 块\",\n            link: \"/zh-cn/10-minimum-example/093-compile-sfc-script\",\n          },\n          {\n            text: \"SFC style 块\",\n            link: \"/zh-cn/10-minimum-example/094-compile-sfc-style\",\n          },\n          {\n            text: \"稍作休息\",\n            link: \"/zh-cn/10-minimum-example/100-break\",\n          },\n        ],\n      },\n      {\n        text: \"基础虚拟 DOM\",\n        collapsed: false,\n        items: [\n          {\n            text: \"key 属性和补丁渲染\",\n            link: \"/zh-cn/20-basic-virtual-dom/010-patch-keyed-children\",\n          },\n          {\n            text: \"VNode 的位级表示\",\n            link: \"/zh-cn/20-basic-virtual-dom/020-bit-flags\",\n          },\n          {\n            text: \"调度器\",\n            link: \"/zh-cn/20-basic-virtual-dom/030-scheduler\",\n          },\n          {\n            text: \"未处理 Props 的补丁\",\n            link: \"/zh-cn/20-basic-virtual-dom/040-patch-other-attrs\",\n          },\n        ],\n      },\n      {\n        text: \"基础响应式系统\",\n        collapsed: false,\n        items: [\n          {\n            text: \"响应式优化\",\n            link: \"/zh-cn/30-basic-reactivity-system/005-reactivity-optimization\",\n          },\n          {\n            text: \"ref API\",\n            link: \"/zh-cn/30-basic-reactivity-system/010-ref-api\",\n          },\n          {\n            text: \"computed / watch API\",\n            link: \"/zh-cn/30-basic-reactivity-system/020-computed-watch\",\n          },\n          {\n            text: \"各种响应式代理处理器\",\n            link: \"/zh-cn/30-basic-reactivity-system/030-reactive-proxy-handlers\",\n          },\n          {\n            text: \"Effect 清理和 Effect 作用域\",\n            link: \"/zh-cn/30-basic-reactivity-system/040-effect-scope\",\n          },\n          {\n            text: \"其他响应式 API\",\n            link: \"/zh-cn/30-basic-reactivity-system/050-other-apis\",\n          },\n        ],\n      },\n      {\n        text: \"基础组件系统\",\n        collapsed: false,\n        items: [\n          {\n            text: \"生命周期钩子\",\n            link: \"/zh-cn/40-basic-component-system/010-lifecycle-hooks\",\n          },\n          {\n            text: \"Provide/Inject\",\n            link: \"/zh-cn/40-basic-component-system/020-provide-inject\",\n          },\n          {\n            text: \"组件代理和 setupContext\",\n            link: \"/zh-cn/40-basic-component-system/030-component-proxy-setup-context\",\n          },\n          {\n            text: \"插槽\",\n            link: \"/zh-cn/40-basic-component-system/040-component-slot\",\n          },\n          {\n            text: \"支持 Options API\",\n            link: \"/zh-cn/40-basic-component-system/050-options-api\",\n          },\n        ],\n      },\n      {\n        text: \"基础模板编译器\",\n        collapsed: false,\n        items: [\n          {\n            text: \"重构 Transformer 的 Codegen 实现\",\n            link: \"/zh-cn/50-basic-template-compiler/010-transform\",\n          },\n          {\n            text: \"实现指令（v-bind）\",\n            link: \"/zh-cn/50-basic-template-compiler/020-v-bind\",\n          },\n          {\n            text: \"在模板中求值表达式\",\n            link: \"/zh-cn/50-basic-template-compiler/022-transform-expression\",\n          },\n          {\n            text: \"支持 v-on\",\n            link: \"/zh-cn/50-basic-template-compiler/025-v-on\",\n          },\n          {\n            text: \"compiler-dom 和事件修饰符\",\n            link: \"/zh-cn/50-basic-template-compiler/027-event-modifier\",\n          },\n          {\n            text: \"支持 Fragment\",\n            link: \"/zh-cn/50-basic-template-compiler/030-fragment\",\n          },\n          {\n            text: \"支持注释节点\",\n            link: \"/zh-cn/50-basic-template-compiler/035-comment\",\n          },\n          {\n            text: \"v-if 和结构指令\",\n            link: \"/zh-cn/50-basic-template-compiler/040-v-if-and-structural-directive\",\n          },\n          {\n            text: \"支持 v-for\",\n            link: \"/zh-cn/50-basic-template-compiler/050-v-for\",\n          },\n          {\n            text: \"解析组件\",\n            link: \"/zh-cn/50-basic-template-compiler/070-resolve-component\",\n          },\n          {\n            text: \"支持插槽（定义）\",\n            link: \"/zh-cn/50-basic-template-compiler/080-component-slot-outlet\",\n          },\n          {\n            text: \"支持插槽（使用）\",\n            link: \"/zh-cn/50-basic-template-compiler/085-component-slot-insert\",\n          },\n          {\n            text: \"其他指令\",\n            link: \"/zh-cn/50-basic-template-compiler/090-other-directives\",\n          },\n          {\n            text: \"编译器细节优化\",\n            link: \"/zh-cn/50-basic-template-compiler/100-chore-compiler\",\n          },\n          {\n            text: \"解析器优化\",\n            link: \"/zh-cn/50-basic-template-compiler/110-parser-optimization\",\n          },\n          {\n            text: \"自定义指令\",\n            link: \"/zh-cn/50-basic-template-compiler/500-custom-directive\",\n          },\n        ],\n      },\n      {\n        text: \"基础 SFC 编译器\",\n        collapsed: false,\n        items: [\n          {\n            text: \"支持 script setup\",\n            link: \"/zh-cn/60-basic-sfc-compiler/010-script-setup\",\n          },\n          {\n            text: \"支持 defineProps\",\n            link: \"/zh-cn/60-basic-sfc-compiler/020-define-props\",\n          },\n          {\n            text: \"支持 defineEmits\",\n            link: \"/zh-cn/60-basic-sfc-compiler/030-define-emits\",\n          },\n          {\n            text: \"支持作用域 CSS\",\n            link: \"/zh-cn/60-basic-sfc-compiler/040-scoped-css\",\n          },\n          {\n            text: \"支持 Props 解构\",\n            link: \"/zh-cn/60-basic-sfc-compiler/050-props-destructure\",\n          },\n          {\n            text: \"基于类型的 defineProps/defineEmits\",\n            link: \"/zh-cn/60-basic-sfc-compiler/060-type-based-macros\",\n          },\n        ],\n      },\n      {\n        text: \"Web 应用程序要点\",\n        collapsed: false,\n        items: [\n          {\n            text: \"插件\",\n            collapsed: false,\n            items: [\n              {\n                text: \"路由器\",\n                link: \"/zh-cn/90-web-application-essentials/010-plugins/010-router\",\n              },\n              {\n                text: \"CSS 预处理器\",\n                link: \"/zh-cn/90-web-application-essentials/010-plugins/020-preprocessors\",\n              },\n              {\n                text: \"状态管理\",\n                link: \"/zh-cn/90-web-application-essentials/010-plugins/020-store\",\n              },\n              {\n                text: \"数据获取\",\n                link: \"/zh-cn/90-web-application-essentials/010-plugins/030-data-fetch\",\n              },\n              {\n                text: \"Language Tools\",\n                link: \"/zh-cn/90-web-application-essentials/010-plugins/040-language-tools\",\n              },\n            ],\n          },\n          {\n            text: \"服务端渲染\",\n            collapsed: false,\n            items: [\n              {\n                text: \"renderToString\",\n                link: \"/zh-cn/90-web-application-essentials/020-ssr/010-create-ssr-app\",\n              },\n              {\n                text: \"Hydration（水合）\",\n                link: \"/zh-cn/90-web-application-essentials/020-ssr/020-hydration\",\n              },\n              {\n                text: \"Compiler SSR\",\n                link: \"/zh-cn/90-web-application-essentials/020-ssr/030-compiler-ssr\",\n              },\n            ],\n          },\n          {\n            text: \"内置组件\",\n            collapsed: false,\n            items: [\n              {\n                text: \"KeepAlive\",\n                link: \"/zh-cn/90-web-application-essentials/030-builtins/010-keep-alive\",\n              },\n              {\n                text: \"Transition\",\n                link: \"/zh-cn/90-web-application-essentials/030-builtins/030-transition\",\n              },\n            ],\n          },\n          {\n            text: \"优化\",\n            collapsed: false,\n            items: [\n              {\n                text: \"静态提升\",\n                link: \"/zh-cn/90-web-application-essentials/040-optimizations/010-static-hoisting\",\n              },\n              {\n                text: \"补丁标志\",\n                link: \"/zh-cn/90-web-application-essentials/040-optimizations/020-patch-flags\",\n              },\n              {\n                text: \"树扁平化\",\n                link: \"/zh-cn/90-web-application-essentials/040-optimizations/030-tree-flattening\",\n              },\n            ],\n          },\n          {\n            text: \"Vapor 模式\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Vapor 模式\",\n                link: \"/zh-cn/90-web-application-essentials/050-vapor/010-introduction\",\n              },\n              {\n                text: \"Vapor 编译器\",\n                link: \"/zh-cn/90-web-application-essentials/050-vapor/020-vapor-compiler\",\n              },\n              {\n                text: \"Vapor SSR\",\n                link: \"/zh-cn/90-web-application-essentials/050-vapor/030-vapor-ssr\",\n              },\n            ],\n          },\n        ],\n      },\n      {\n        text: \"附录\",\n        collapsed: false,\n        items: [\n          {\n            text: \"15分钟编写 Vue.js\",\n            collapsed: false,\n            items: [\n              {\n                text: \"chibivue，不是很小吗...？\",\n                link: \"/zh-cn/bonus/hyper-ultimate-super-extreme-minimal-vue/\",\n              },\n              {\n                text: \"实现\",\n                link: \"/zh-cn/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl\",\n              },\n            ],\n          },\n          {\n            text: \"调试原始 Vue.js 源码\",\n            link: \"/zh-cn/bonus/debug-vuejs-core\",\n          },\n        ],\n      },\n    ],\n    editLink: {\n      pattern: \"https://github.com/chibivue-land/chibivue/blob/main/book/online-book/src/:path\",\n      text: \"在 GitHub 上编辑此页面\",\n    },\n    footer: {\n      message: \"基于 MIT 许可证发布。\",\n      copyright: \"Copyright © 2023-present ubugeeei\",\n    },\n    docFooter: {\n      prev: \"上一页\",\n      next: \"下一页\",\n    },\n    outline: {\n      label: \"页面导航\",\n    },\n    lastUpdated: {\n      text: \"最后更新于\",\n      formatOptions: {\n        dateStyle: \"short\",\n        timeStyle: \"medium\",\n      },\n    },\n    langMenuLabel: \"多语言\",\n    returnToTopLabel: \"回到顶部\",\n    sidebarMenuLabel: \"菜单\",\n    darkModeSwitchLabel: \"主题\",\n    lightModeSwitchTitle: \"切换到浅色模式\",\n    darkModeSwitchTitle: \"切换到深色模式\",\n  },\n};\n"
  },
  {
    "path": "book/online-book/.vitepress/config/zh-tw.ts",
    "content": "import type { DefaultTheme, LocaleSpecificConfig } from \"vitepress\";\n\nexport const zhTwConfig: LocaleSpecificConfig<DefaultTheme.Config> = {\n  themeConfig: {\n    nav: [\n      { text: \"首頁\", link: \"/zh-tw/\" },\n      { text: \"開始學習\", link: \"/zh-tw/00-introduction/010-about\" },\n    ],\n    sidebar: [\n      {\n        text: \"入門指南\",\n        collapsed: false,\n        items: [\n          { text: \"入門指南\", link: \"/zh-tw/00-introduction/010-about\" },\n          {\n            text: \"什麼是 Vue.js？\",\n            link: \"/zh-tw/00-introduction/020-what-is-vue\",\n          },\n          {\n            text: \"Vue.js 的關鍵要素\",\n            link: \"/zh-tw/00-introduction/030-vue-core-components\",\n          },\n          {\n            text: \"本書的方法和環境設置\",\n            link: \"/zh-tw/00-introduction/040-setup-project\",\n          },\n        ],\n      },\n      {\n        text: \"最小示例\",\n        collapsed: false,\n        items: [\n          {\n            text: \"第一次渲染和 createApp API\",\n            link: \"/zh-tw/10-minimum-example/010-create-app-api\",\n          },\n          {\n            text: \"套件架構\",\n            link: \"/zh-tw/10-minimum-example/015-package-architecture\",\n          },\n          {\n            text: \"讓我們啟用 HTML 元素渲染\",\n            link: \"/zh-tw/10-minimum-example/020-simple-h-function\",\n          },\n          {\n            text: \"讓我們支援事件處理器和屬性\",\n            link: \"/zh-tw/10-minimum-example/025-event-handler-and-attrs\",\n          },\n          {\n            text: \"響應式系統的前置知識\",\n            link: \"/zh-tw/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system\",\n          },\n          {\n            text: \"嘗試實現一個小型響應式系統\",\n            link: \"/zh-tw/10-minimum-example/035-try-implementing-a-minimum-reactivity-system\",\n          },\n          {\n            text: \"最小虛擬 DOM\",\n            link: \"/zh-tw/10-minimum-example/040-minimum-virtual-dom\",\n          },\n          {\n            text: \"追求組件導向開發\",\n            link: \"/zh-tw/10-minimum-example/050-minimum-component\",\n          },\n          {\n            text: \"組件 Props\",\n            link: \"/zh-tw/10-minimum-example/051-component-props\",\n          },\n          {\n            text: \"組件 Emit\",\n            link: \"/zh-tw/10-minimum-example/052-component-emits\",\n          },\n          {\n            text: \"理解模板編譯器\",\n            link: \"/zh-tw/10-minimum-example/060-template-compiler\",\n          },\n          {\n            text: \"實現模板編譯器\",\n            link: \"/zh-tw/10-minimum-example/061-template-compiler-impl\",\n          },\n          {\n            text: \"希望編寫更複雜的 HTML\",\n            link: \"/zh-tw/10-minimum-example/070-more-complex-parser\",\n          },\n          {\n            text: \"資料綁定\",\n            link: \"/zh-tw/10-minimum-example/080-template-binding\",\n          },\n          {\n            text: \"使用 SFC 開發（外圍知識）\",\n            link: \"/zh-tw/10-minimum-example/090-prerequisite-knowledge-for-the-sfc\",\n          },\n          {\n            text: \"解析 SFC\",\n            link: \"/zh-tw/10-minimum-example/091-parse-sfc\",\n          },\n          {\n            text: \"SFC template 區塊\",\n            link: \"/zh-tw/10-minimum-example/092-compile-sfc-template\",\n          },\n          {\n            text: \"SFC script 區塊\",\n            link: \"/zh-tw/10-minimum-example/093-compile-sfc-script\",\n          },\n          {\n            text: \"SFC style 區塊\",\n            link: \"/zh-tw/10-minimum-example/094-compile-sfc-style\",\n          },\n          {\n            text: \"稍作休息\",\n            link: \"/zh-tw/10-minimum-example/100-break\",\n          },\n        ],\n      },\n      {\n        text: \"基礎虛擬 DOM\",\n        collapsed: false,\n        items: [\n          {\n            text: \"key 屬性和補丁渲染\",\n            link: \"/zh-tw/20-basic-virtual-dom/010-patch-keyed-children\",\n          },\n          {\n            text: \"VNode 的位元級表示\",\n            link: \"/zh-tw/20-basic-virtual-dom/020-bit-flags\",\n          },\n          {\n            text: \"調度器\",\n            link: \"/zh-tw/20-basic-virtual-dom/030-scheduler\",\n          },\n          {\n            text: \"未處理 Props 的補丁\",\n            link: \"/zh-tw/20-basic-virtual-dom/040-patch-other-attrs\",\n          },\n        ],\n      },\n      {\n        text: \"基礎響應式系統\",\n        collapsed: false,\n        items: [\n          {\n            text: \"響應式最佳化\",\n            link: \"/zh-tw/30-basic-reactivity-system/005-reactivity-optimization\",\n          },\n          {\n            text: \"ref API\",\n            link: \"/zh-tw/30-basic-reactivity-system/010-ref-api\",\n          },\n          {\n            text: \"computed / watch API\",\n            link: \"/zh-tw/30-basic-reactivity-system/020-computed-watch\",\n          },\n          {\n            text: \"各種響應式代理處理器\",\n            link: \"/zh-tw/30-basic-reactivity-system/030-reactive-proxy-handlers\",\n          },\n          {\n            text: \"Effect 清理和 Effect 作用域\",\n            link: \"/zh-tw/30-basic-reactivity-system/040-effect-scope\",\n          },\n          {\n            text: \"其他響應式 API\",\n            link: \"/zh-tw/30-basic-reactivity-system/050-other-apis\",\n          },\n        ],\n      },\n      {\n        text: \"基礎組件系統\",\n        collapsed: false,\n        items: [\n          {\n            text: \"生命週期鉤子\",\n            link: \"/zh-tw/40-basic-component-system/010-lifecycle-hooks\",\n          },\n          {\n            text: \"Provide/Inject\",\n            link: \"/zh-tw/40-basic-component-system/020-provide-inject\",\n          },\n          {\n            text: \"組件代理和 setupContext\",\n            link: \"/zh-tw/40-basic-component-system/030-component-proxy-setup-context\",\n          },\n          {\n            text: \"插槽\",\n            link: \"/zh-tw/40-basic-component-system/040-component-slot\",\n          },\n          {\n            text: \"支援 Options API\",\n            link: \"/zh-tw/40-basic-component-system/050-options-api\",\n          },\n        ],\n      },\n      {\n        text: \"基礎模板編譯器\",\n        collapsed: false,\n        items: [\n          {\n            text: \"重構 Transformer 的 Codegen 實現\",\n            link: \"/zh-tw/50-basic-template-compiler/010-transform\",\n          },\n          {\n            text: \"實現指令（v-bind）\",\n            link: \"/zh-tw/50-basic-template-compiler/020-v-bind\",\n          },\n          {\n            text: \"在模板中求值表達式\",\n            link: \"/zh-tw/50-basic-template-compiler/022-transform-expression\",\n          },\n          {\n            text: \"支援 v-on\",\n            link: \"/zh-tw/50-basic-template-compiler/025-v-on\",\n          },\n          {\n            text: \"compiler-dom 和事件修飾符\",\n            link: \"/zh-tw/50-basic-template-compiler/027-event-modifier\",\n          },\n          {\n            text: \"支援 Fragment\",\n            link: \"/zh-tw/50-basic-template-compiler/030-fragment\",\n          },\n          {\n            text: \"支援註釋節點\",\n            link: \"/zh-tw/50-basic-template-compiler/035-comment\",\n          },\n          {\n            text: \"v-if 和結構指令\",\n            link: \"/zh-tw/50-basic-template-compiler/040-v-if-and-structural-directive\",\n          },\n          {\n            text: \"支援 v-for\",\n            link: \"/zh-tw/50-basic-template-compiler/050-v-for\",\n          },\n          {\n            text: \"解析組件\",\n            link: \"/zh-tw/50-basic-template-compiler/070-resolve-component\",\n          },\n          {\n            text: \"支援插槽（定義）\",\n            link: \"/zh-tw/50-basic-template-compiler/080-component-slot-outlet\",\n          },\n          {\n            text: \"支援插槽（使用）\",\n            link: \"/zh-tw/50-basic-template-compiler/085-component-slot-insert\",\n          },\n          {\n            text: \"其他指令\",\n            link: \"/zh-tw/50-basic-template-compiler/090-other-directives\",\n          },\n          {\n            text: \"編譯器細節優化\",\n            link: \"/zh-tw/50-basic-template-compiler/100-chore-compiler\",\n          },\n          {\n            text: \"解析器優化\",\n            link: \"/zh-tw/50-basic-template-compiler/110-parser-optimization\",\n          },\n          {\n            text: \"自訂指令\",\n            link: \"/zh-tw/50-basic-template-compiler/500-custom-directive\",\n          },\n        ],\n      },\n      {\n        text: \"基礎 SFC 編譯器\",\n        collapsed: false,\n        items: [\n          {\n            text: \"支援 script setup\",\n            link: \"/zh-tw/60-basic-sfc-compiler/010-script-setup\",\n          },\n          {\n            text: \"支援 defineProps\",\n            link: \"/zh-tw/60-basic-sfc-compiler/020-define-props\",\n          },\n          {\n            text: \"支援 defineEmits\",\n            link: \"/zh-tw/60-basic-sfc-compiler/030-define-emits\",\n          },\n          {\n            text: \"支援作用域 CSS\",\n            link: \"/zh-tw/60-basic-sfc-compiler/040-scoped-css\",\n          },\n          {\n            text: \"支援 Props 解構\",\n            link: \"/zh-tw/60-basic-sfc-compiler/050-props-destructure\",\n          },\n          {\n            text: \"基於類型的 defineProps/defineEmits\",\n            link: \"/zh-tw/60-basic-sfc-compiler/060-type-based-macros\",\n          },\n        ],\n      },\n      {\n        text: \"Web 應用程式要點\",\n        collapsed: false,\n        items: [\n          {\n            text: \"外掛\",\n            collapsed: false,\n            items: [\n              {\n                text: \"路由器\",\n                link: \"/zh-tw/90-web-application-essentials/010-plugins/010-router\",\n              },\n              {\n                text: \"CSS 預處理器\",\n                link: \"/zh-tw/90-web-application-essentials/010-plugins/020-preprocessors\",\n              },\n              {\n                text: \"狀態管理\",\n                link: \"/zh-tw/90-web-application-essentials/010-plugins/020-store\",\n              },\n              {\n                text: \"資料獲取\",\n                link: \"/zh-tw/90-web-application-essentials/010-plugins/030-data-fetch\",\n              },\n              {\n                text: \"Language Tools\",\n                link: \"/zh-tw/90-web-application-essentials/010-plugins/040-language-tools\",\n              },\n            ],\n          },\n          {\n            text: \"伺服器端渲染\",\n            collapsed: false,\n            items: [\n              {\n                text: \"renderToString\",\n                link: \"/zh-tw/90-web-application-essentials/020-ssr/010-create-ssr-app\",\n              },\n              {\n                text: \"Hydration（水合）\",\n                link: \"/zh-tw/90-web-application-essentials/020-ssr/020-hydration\",\n              },\n              {\n                text: \"Compiler SSR\",\n                link: \"/zh-tw/90-web-application-essentials/020-ssr/030-compiler-ssr\",\n              },\n            ],\n          },\n          {\n            text: \"內建組件\",\n            collapsed: false,\n            items: [\n              {\n                text: \"KeepAlive\",\n                link: \"/zh-tw/90-web-application-essentials/030-builtins/010-keep-alive\",\n              },\n              {\n                text: \"Transition\",\n                link: \"/zh-tw/90-web-application-essentials/030-builtins/030-transition\",\n              },\n            ],\n          },\n          {\n            text: \"最佳化\",\n            collapsed: false,\n            items: [\n              {\n                text: \"靜態提升\",\n                link: \"/zh-tw/90-web-application-essentials/040-optimizations/010-static-hoisting\",\n              },\n              {\n                text: \"補丁標誌\",\n                link: \"/zh-tw/90-web-application-essentials/040-optimizations/020-patch-flags\",\n              },\n              {\n                text: \"樹扁平化\",\n                link: \"/zh-tw/90-web-application-essentials/040-optimizations/030-tree-flattening\",\n              },\n            ],\n          },\n          {\n            text: \"Vapor 模式\",\n            collapsed: false,\n            items: [\n              {\n                text: \"Vapor 模式\",\n                link: \"/zh-tw/90-web-application-essentials/050-vapor/010-introduction\",\n              },\n              {\n                text: \"Vapor 編譯器\",\n                link: \"/zh-tw/90-web-application-essentials/050-vapor/020-vapor-compiler\",\n              },\n              {\n                text: \"Vapor SSR\",\n                link: \"/zh-tw/90-web-application-essentials/050-vapor/030-vapor-ssr\",\n              },\n            ],\n          },\n        ],\n      },\n      {\n        text: \"附錄\",\n        collapsed: false,\n        items: [\n          {\n            text: \"15分鐘編寫 Vue.js\",\n            collapsed: false,\n            items: [\n              {\n                text: \"chibivue，不是很小嗎...？\",\n                link: \"/zh-tw/bonus/hyper-ultimate-super-extreme-minimal-vue/\",\n              },\n              {\n                text: \"實現\",\n                link: \"/zh-tw/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl\",\n              },\n            ],\n          },\n          {\n            text: \"除錯原始 Vue.js 原始碼\",\n            link: \"/zh-tw/bonus/debug-vuejs-core\",\n          },\n        ],\n      },\n    ],\n    editLink: {\n      pattern: \"https://github.com/chibivue-land/chibivue/blob/main/book/online-book/src/:path\",\n      text: \"在 GitHub 上編輯此頁面\",\n    },\n    footer: {\n      message: \"基於 MIT 許可證發布。\",\n      copyright: \"Copyright © 2023-present ubugeeei\",\n    },\n    docFooter: {\n      prev: \"上一頁\",\n      next: \"下一頁\",\n    },\n    outline: {\n      label: \"頁面導航\",\n    },\n    lastUpdated: {\n      text: \"最後更新於\",\n      formatOptions: {\n        dateStyle: \"short\",\n        timeStyle: \"medium\",\n      },\n    },\n    langMenuLabel: \"多語言\",\n    returnToTopLabel: \"回到頂部\",\n    sidebarMenuLabel: \"選單\",\n    darkModeSwitchLabel: \"主題\",\n    lightModeSwitchTitle: \"切換到淺色模式\",\n    darkModeSwitchTitle: \"切換到深色模式\",\n  },\n};\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/Layout.vue",
    "content": "<script setup lang=\"ts\">\nimport DefaultTheme from 'vitepress/theme'\nimport { useData } from 'vitepress'\nimport CustomHome from './components/CustomHome.vue'\nimport SidebarEnhancer from './components/SidebarEnhancer.vue'\nimport WipBanner from './components/WipBanner.vue'\n\nconst { Layout } = DefaultTheme\nconst { frontmatter } = useData()\n</script>\n\n<template>\n  <Layout>\n    <template #home-hero-before>\n      <CustomHome v-if=\"frontmatter.layout === 'home'\" />\n    </template>\n    <template #doc-before>\n      <WipBanner />\n    </template>\n    <template #layout-bottom>\n      <SidebarEnhancer />\n    </template>\n  </Layout>\n</template>\n\n<style>\n/* Hide default VitePress home elements when custom home is shown */\n.VPHome .VPHero,\n.VPHome .VPFeatures {\n  display: none !important;\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/chibivue-theme-light.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/schema.json\",\n  \"name\": \"chibivue-terminal-light\",\n  \"displayName\": \"Chibivue Terminal Light\",\n  \"type\": \"light\",\n  \"colors\": {\n    \"editor.background\": \"#f5f5f0\",\n    \"editor.foreground\": \"#1a5c4a\",\n    \"editor.lineHighlightBackground\": \"#e8f0ed\",\n    \"editor.selectionBackground\": \"#2cc9a833\",\n    \"editorCursor.foreground\": \"#1a8a6e\",\n    \"editorWhitespace.foreground\": \"#d0d8d5\"\n  },\n  \"tokenColors\": [\n    {\n      \"scope\": [\"comment\", \"punctuation.definition.comment\"],\n      \"settings\": {\n        \"foreground\": \"#5a7a6a\",\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\"string\", \"string.quoted\", \"string.template\"],\n      \"settings\": {\n        \"foreground\": \"#b8860b\"\n      }\n    },\n    {\n      \"scope\": [\"constant.numeric\", \"constant.language.boolean\"],\n      \"settings\": {\n        \"foreground\": \"#c06000\"\n      }\n    },\n    {\n      \"scope\": [\"constant.language\", \"constant.other\"],\n      \"settings\": {\n        \"foreground\": \"#c06000\"\n      }\n    },\n    {\n      \"scope\": [\n        \"keyword\",\n        \"keyword.control\",\n        \"keyword.operator.new\",\n        \"keyword.operator.expression\",\n        \"storage.type\",\n        \"storage.modifier\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#1a8a6e\"\n      }\n    },\n    {\n      \"scope\": [\"keyword.operator\", \"punctuation\"],\n      \"settings\": {\n        \"foreground\": \"#2a5a4a\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.function\",\n        \"meta.function-call\",\n        \"support.function\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#0d7d6c\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.type\",\n        \"entity.name.class\",\n        \"support.class\",\n        \"support.type\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#2a7a4a\"\n      }\n    },\n    {\n      \"scope\": [\"entity.name.tag\", \"meta.tag\"],\n      \"settings\": {\n        \"foreground\": \"#1a8a6e\"\n      }\n    },\n    {\n      \"scope\": [\"entity.other.attribute-name\"],\n      \"settings\": {\n        \"foreground\": \"#1a5c4a\"\n      }\n    },\n    {\n      \"scope\": [\n        \"variable\",\n        \"variable.other\",\n        \"variable.parameter\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#2a5a4a\"\n      }\n    },\n    {\n      \"scope\": [\"variable.language.this\", \"variable.language.super\"],\n      \"settings\": {\n        \"foreground\": \"#c04050\",\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\n        \"meta.import\",\n        \"keyword.control.import\",\n        \"keyword.control.export\",\n        \"keyword.control.from\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#3a5a7a\"\n      }\n    },\n    {\n      \"scope\": [\"support.constant\", \"constant.other.placeholder\"],\n      \"settings\": {\n        \"foreground\": \"#c06000\"\n      }\n    },\n    {\n      \"scope\": [\"meta.object-literal.key\"],\n      \"settings\": {\n        \"foreground\": \"#0a5a4a\"\n      }\n    },\n    {\n      \"scope\": [\"punctuation.definition.template-expression\"],\n      \"settings\": {\n        \"foreground\": \"#1a8a6e\"\n      }\n    },\n    {\n      \"scope\": [\"entity.name.tag.html\", \"entity.name.tag.xml\"],\n      \"settings\": {\n        \"foreground\": \"#1a8a6e\"\n      }\n    },\n    {\n      \"scope\": [\"punctuation.definition.tag\"],\n      \"settings\": {\n        \"foreground\": \"#1a6a5a\"\n      }\n    },\n    {\n      \"scope\": [\"text.html.vue-html meta.tag.other.unrecognized\"],\n      \"settings\": {\n        \"foreground\": \"#0d7d6c\"\n      }\n    },\n    {\n      \"scope\": [\n        \"source.directive\",\n        \"entity.other.attribute-name.directive\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#2a7a4a\"\n      }\n    },\n    {\n      \"scope\": [\"markup.heading\"],\n      \"settings\": {\n        \"foreground\": \"#1a8a6e\",\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"scope\": [\"markup.bold\"],\n      \"settings\": {\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"scope\": [\"markup.italic\"],\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\"markup.inline.raw\", \"markup.fenced_code\"],\n      \"settings\": {\n        \"foreground\": \"#b8860b\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/chibivue-theme.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/schema.json\",\n  \"name\": \"chibivue-terminal\",\n  \"displayName\": \"Chibivue Terminal\",\n  \"type\": \"dark\",\n  \"colors\": {\n    \"editor.background\": \"#0a0f14\",\n    \"editor.foreground\": \"#8be4d3\",\n    \"editor.lineHighlightBackground\": \"#1a2744\",\n    \"editor.selectionBackground\": \"#2cc9a855\",\n    \"editorCursor.foreground\": \"#2cc9a8\",\n    \"editorWhitespace.foreground\": \"#1f2d40\"\n  },\n  \"tokenColors\": [\n    {\n      \"scope\": [\"comment\", \"punctuation.definition.comment\"],\n      \"settings\": {\n        \"foreground\": \"#4a5f87\",\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\"string\", \"string.quoted\", \"string.template\"],\n      \"settings\": {\n        \"foreground\": \"#f4d35e\"\n      }\n    },\n    {\n      \"scope\": [\"constant.numeric\", \"constant.language.boolean\"],\n      \"settings\": {\n        \"foreground\": \"#e8a545\"\n      }\n    },\n    {\n      \"scope\": [\"constant.language\", \"constant.other\"],\n      \"settings\": {\n        \"foreground\": \"#e8a545\"\n      }\n    },\n    {\n      \"scope\": [\n        \"keyword\",\n        \"keyword.control\",\n        \"keyword.operator.new\",\n        \"keyword.operator.expression\",\n        \"storage.type\",\n        \"storage.modifier\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#2cc9a8\"\n      }\n    },\n    {\n      \"scope\": [\"keyword.operator\", \"punctuation\"],\n      \"settings\": {\n        \"foreground\": \"#6b9e8a\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.function\",\n        \"meta.function-call\",\n        \"support.function\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#5dddc2\"\n      }\n    },\n    {\n      \"scope\": [\n        \"entity.name.type\",\n        \"entity.name.class\",\n        \"support.class\",\n        \"support.type\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#8ae99c\"\n      }\n    },\n    {\n      \"scope\": [\"entity.name.tag\", \"meta.tag\"],\n      \"settings\": {\n        \"foreground\": \"#2cc9a8\"\n      }\n    },\n    {\n      \"scope\": [\"entity.other.attribute-name\"],\n      \"settings\": {\n        \"foreground\": \"#8be4d3\"\n      }\n    },\n    {\n      \"scope\": [\n        \"variable\",\n        \"variable.other\",\n        \"variable.parameter\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#b8e4d8\"\n      }\n    },\n    {\n      \"scope\": [\"variable.language.this\", \"variable.language.super\"],\n      \"settings\": {\n        \"foreground\": \"#e85d75\",\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\n        \"meta.import\",\n        \"keyword.control.import\",\n        \"keyword.control.export\",\n        \"keyword.control.from\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#9aaac4\"\n      }\n    },\n    {\n      \"scope\": [\"support.constant\", \"constant.other.placeholder\"],\n      \"settings\": {\n        \"foreground\": \"#e8a545\"\n      }\n    },\n    {\n      \"scope\": [\"meta.object-literal.key\"],\n      \"settings\": {\n        \"foreground\": \"#50d6bd\"\n      }\n    },\n    {\n      \"scope\": [\"punctuation.definition.template-expression\"],\n      \"settings\": {\n        \"foreground\": \"#2cc9a8\"\n      }\n    },\n    {\n      \"scope\": [\"entity.name.tag.html\", \"entity.name.tag.xml\"],\n      \"settings\": {\n        \"foreground\": \"#2cc9a8\"\n      }\n    },\n    {\n      \"scope\": [\"punctuation.definition.tag\"],\n      \"settings\": {\n        \"foreground\": \"#4a8a7a\"\n      }\n    },\n    {\n      \"scope\": [\"text.html.vue-html meta.tag.other.unrecognized\"],\n      \"settings\": {\n        \"foreground\": \"#5dddc2\"\n      }\n    },\n    {\n      \"scope\": [\n        \"source.directive\",\n        \"entity.other.attribute-name.directive\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#8ae99c\"\n      }\n    },\n    {\n      \"scope\": [\"markup.heading\"],\n      \"settings\": {\n        \"foreground\": \"#2cc9a8\",\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"scope\": [\"markup.bold\"],\n      \"settings\": {\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"scope\": [\"markup.italic\"],\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": [\"markup.inline.raw\", \"markup.fenced_code\"],\n      \"settings\": {\n        \"foreground\": \"#f4d35e\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/CustomHome.vue",
    "content": "<script setup lang=\"ts\">\nimport { useData, useRoute } from 'vitepress'\nimport { computed } from 'vue'\nimport HeroSection from './HeroSection.vue'\nimport FeaturesSection from './FeaturesSection.vue'\n\nconst { lang } = useData()\nconst route = useRoute()\n\n// 現在の言語を取得\nconst currentLang = computed(() => {\n  const path = route.path\n  if (path.startsWith('/zh-cn')) return 'zh-cn'\n  if (path.startsWith('/zh-tw')) return 'zh-tw'\n  if (path.startsWith('/ja')) return 'ja'\n  return 'en'\n})\n\nconst heroContent: Record<\n  string,\n  {\n    tagline: string\n    startButton: string\n    vueButton: string\n  }\n> = {\n  en: {\n    tagline:\n      \"Writing Vue.js: Step by Step, from just one line of 'Hello, World'.\",\n    startButton: 'Start Learning',\n    vueButton: 'Vue.js Official',\n  },\n  ja: {\n    tagline:\n      \"Writing Vue.js: Step by Step, from just one line of 'Hello, World'.\",\n    startButton: 'はじめる',\n    vueButton: 'Vue.js 公式',\n  },\n  'zh-cn': {\n    tagline: \"编写 Vue.js：从一行 'Hello, World' 开始，循序渐进。\",\n    startButton: '开始学习',\n    vueButton: 'Vue.js 官方',\n  },\n  'zh-tw': {\n    tagline: \"編寫 Vue.js：從一行 'Hello, World' 開始，循序漸進。\",\n    startButton: '開始學習',\n    vueButton: 'Vue.js 官方',\n  },\n}\n\nconst featuresContent: Record<\n  string,\n  Array<{\n    icon: string\n    title: string\n    description: string\n  }>\n> = {\n  en: [\n    {\n      icon: 'reactivity',\n      title: 'Reactivity System',\n      description:\n        'From the basic principles of the Reactivity System, we will cover a wide range of implementations, from EffectScope to advanced APIs like CustomRef.',\n    },\n    {\n      icon: 'vdom',\n      title: 'Virtual DOM',\n      description:\n        'We will cover a broad range of implementations, from the basic setup of the Virtual DOM to patch rendering and scheduler implementations.',\n    },\n    {\n      icon: 'compiler',\n      title: 'Template Compiler',\n      description:\n        'From the fundamental implementation of the template compiler, we will extend our coverage to data binding and directive implementations.',\n    },\n    {\n      icon: 'sfc',\n      title: 'Single File Component',\n      description:\n        'Starting from the basic implementation of SFCs, we will delve into a wide range of areas, from script setup to compiler macros and scoped CSS implementations.',\n    },\n  ],\n  ja: [\n    {\n      icon: 'reactivity',\n      title: 'Reactivity System',\n      description:\n        'Reactivity System の基本原理から、EffectScope や CustomRef などの高度な API の実装まで、幅広くカバーします。',\n    },\n    {\n      icon: 'vdom',\n      title: 'Virtual DOM',\n      description:\n        'Virtual DOM の基本的なセットアップから、パッチレンダリングやスケジューラの実装まで、幅広くカバーします。',\n    },\n    {\n      icon: 'compiler',\n      title: 'Template Compiler',\n      description:\n        'テンプレートコンパイラの基本的な実装から、データバインディングやディレクティブの実装まで拡張します。',\n    },\n    {\n      icon: 'sfc',\n      title: 'Single File Component',\n      description:\n        'SFC の基本的な実装から、script setup やコンパイラマクロ、scoped CSS の実装まで、幅広く掘り下げます。',\n    },\n  ],\n  'zh-cn': [\n    {\n      icon: 'reactivity',\n      title: 'Reactivity System',\n      description:\n        '从响应式系统的基本原理到 EffectScope 和 CustomRef 等高级 API 的实现。',\n    },\n    {\n      icon: 'vdom',\n      title: 'Virtual DOM',\n      description: '从虚拟 DOM 的基本设置到 patch 渲染和调度器的实现。',\n    },\n    {\n      icon: 'compiler',\n      title: 'Template Compiler',\n      description: '从模板编译器的基本实现到数据绑定和指令的实现。',\n    },\n    {\n      icon: 'sfc',\n      title: 'Single File Component',\n      description:\n        '从 SFC 的基本实现到 script setup、编译器宏和 scoped CSS 的实现。',\n    },\n  ],\n  'zh-tw': [\n    {\n      icon: 'reactivity',\n      title: 'Reactivity System',\n      description:\n        '從響應式系統的基本原理到 EffectScope 和 CustomRef 等高級 API 的實現。',\n    },\n    {\n      icon: 'vdom',\n      title: 'Virtual DOM',\n      description: '從虛擬 DOM 的基本設置到 patch 渲染和調度器的實現。',\n    },\n    {\n      icon: 'compiler',\n      title: 'Template Compiler',\n      description: '從模板編譯器的基本實現到數據綁定和指令的實現。',\n    },\n    {\n      icon: 'sfc',\n      title: 'Single File Component',\n      description:\n        '從 SFC 的基本實現到 script setup、編譯器宏和 scoped CSS 的實現。',\n    },\n  ],\n}\n\nconst currentHero = computed(() => heroContent[currentLang.value] || heroContent.en)\nconst currentFeatures = computed(() => featuresContent[currentLang.value] || featuresContent.en)\n\nconst startLink = computed(() =>\n  currentLang.value === 'en'\n    ? '/00-introduction/010-about'\n    : `/${currentLang.value}/00-introduction/010-about`\n)\n</script>\n\n<template>\n  <div class=\"custom-home\">\n    <HeroSection :content=\"currentHero\" :start-link=\"startLink\" />\n    <FeaturesSection :features=\"currentFeatures\" />\n  </div>\n</template>\n\n<style scoped>\n.custom-home {\n  width: 100%;\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/FeatureCard.vue",
    "content": "<script setup lang=\"ts\">\nimport MdiFlash from '~icons/mdi/flash'\nimport MdiFileTree from '~icons/mdi/file-tree'\nimport MdiCog from '~icons/mdi/cog'\nimport MdiPackageVariant from '~icons/mdi/package-variant'\nimport MdiStar from '~icons/mdi/star'\nimport { markRaw, type Component } from 'vue'\n\ndefineProps<{\n  icon: string\n  title: string\n  description: string\n}>()\n\nconst iconMap: Record<string, Component> = {\n  reactivity: markRaw(MdiFlash),\n  vdom: markRaw(MdiFileTree),\n  compiler: markRaw(MdiCog),\n  sfc: markRaw(MdiPackageVariant),\n}\n\nconst defaultIcon = markRaw(MdiStar)\n</script>\n\n<template>\n  <article class=\"feature-card\">\n    <div class=\"feature-icon\">\n      <span class=\"icon-glow\"></span>\n      <component :is=\"iconMap[icon] || defaultIcon\" class=\"icon-content\" />\n    </div>\n    <h3 class=\"feature-title\">{{ title }}</h3>\n    <p class=\"feature-description\">{{ description }}</p>\n    <div class=\"feature-hover-effect\"></div>\n  </article>\n</template>\n\n<style scoped>\n.feature-card {\n  position: relative;\n  padding: 1.75rem;\n  background: linear-gradient(\n    145deg,\n    rgba(26, 39, 68, 0.6) 0%,\n    rgba(15, 26, 46, 0.8) 100%\n  );\n  border: 1px solid rgba(26, 179, 148, 0.15);\n  border-radius: 16px;\n  overflow: hidden;\n  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n.feature-card:hover {\n  transform: translateY(-4px);\n  border-color: rgba(26, 179, 148, 0.4);\n  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 30px rgba(26, 179, 148, 0.1);\n}\n\n\n.feature-icon {\n  position: relative;\n  width: 56px;\n  height: 56px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 1.25rem;\n}\n\n.icon-glow {\n  position: absolute;\n  inset: 0;\n  background: linear-gradient(\n    180deg,\n    var(--c-mint-400, #2cc9a8) 0%,\n    var(--c-mint-500, #1ab394) 100%\n  );\n  border-radius: 12px;\n  opacity: 0.2;\n  filter: blur(8px);\n  transition: opacity 0.3s;\n}\n\n.feature-card:hover .icon-glow {\n  opacity: 0.4;\n}\n\n.icon-content {\n  position: relative;\n  font-size: 1.75rem;\n  z-index: 1;\n}\n\n.feature-title {\n  font-size: 1.15rem;\n  font-weight: 600;\n  color: var(--vp-c-text-1);\n  margin: 0 0 0.75rem 0;\n}\n\n.feature-description {\n  font-size: 0.9rem;\n  color: var(--vp-c-text-2);\n  line-height: 1.7;\n  margin: 0;\n}\n\n.feature-hover-effect {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 2px;\n  background: linear-gradient(\n    90deg,\n    var(--c-mint-500, #1ab394) 0%,\n    var(--c-mint-400, #2cc9a8) 50%,\n    #5dddc2 100%\n  );\n  transform: scaleX(0);\n  transform-origin: left;\n  transition: transform 0.4s ease;\n}\n\n.feature-card:hover .feature-hover-effect {\n  transform: scaleX(1);\n}\n</style>\n\n<style>\n/* Light mode overrides - unscoped */\nhtml:not(.dark) .feature-card {\n  background: linear-gradient(\n    145deg,\n    rgba(255, 255, 255, 0.95) 0%,\n    rgba(248, 252, 250, 0.98) 100%\n  ) !important;\n  border-color: rgba(26, 179, 148, 0.25) !important;\n  box-shadow: 0 4px 16px rgba(26, 179, 148, 0.08);\n}\n\nhtml:not(.dark) .feature-card:hover {\n  box-shadow: 0 12px 32px rgba(26, 179, 148, 0.15), 0 0 24px rgba(26, 179, 148, 0.08) !important;\n  border-color: rgba(26, 179, 148, 0.4) !important;\n}\n\nhtml:not(.dark) .feature-title {\n  color: #1a2744 !important;\n}\n\nhtml:not(.dark) .feature-description {\n  color: #3d4f5f !important;\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/FeaturesSection.vue",
    "content": "<script setup lang=\"ts\">\nimport FeatureCard from './FeatureCard.vue'\n\ndefineProps<{\n  features: Array<{\n    icon: string\n    title: string\n    description: string\n  }>\n}>()\n</script>\n\n<template>\n  <section class=\"features-section\">\n    <div class=\"features-container\">\n      <div class=\"features-grid\">\n        <FeatureCard\n          v-for=\"(feature, index) in features\"\n          :key=\"index\"\n          :icon=\"feature.icon\"\n          :title=\"feature.title\"\n          :description=\"feature.description\"\n        />\n      </div>\n    </div>\n  </section>\n</template>\n\n<style scoped>\n.features-section {\n  padding: 4rem 2rem 6rem;\n  background: var(--features-bg, #0f1724);\n}\n\n.features-container {\n  max-width: 1200px;\n  margin: 0 auto;\n}\n\n.features-grid {\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 1.5rem;\n}\n\n@media (min-width: 640px) {\n  .features-grid {\n    grid-template-columns: repeat(2, 1fr);\n    gap: 2rem;\n  }\n}\n\n@media (min-width: 960px) {\n  .features-grid {\n    grid-template-columns: repeat(4, 1fr);\n  }\n}\n</style>\n\n<style>\n/* Light mode override - unscoped */\nhtml:not(.dark) .features-section {\n  background: linear-gradient(180deg, #f0f5f3 0%, #e8f0ed 100%) !important;\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/HeroSection.vue",
    "content": "<script setup lang=\"ts\">\ndeclare const __CHIBIVUE_VERSION__: string\n\ndefineProps<{\n  content: {\n    tagline: string\n    startButton: string\n    vueButton: string\n  }\n  startLink: string\n}>()\n\nconst version = __CHIBIVUE_VERSION__\n</script>\n\n<template>\n  <section class=\"hero-section\">\n    <div class=\"hero-background\">\n      <div class=\"code-pattern\"></div>\n      <div class=\"gradient-overlay\"></div>\n    </div>\n\n    <div class=\"hero-content\">\n      <div class=\"hero-text\">\n        <h1 class=\"hero-title\">\n          <span class=\"gradient-text\">chibivue</span>\n          <span class=\"version-badge\">v{{ version }}</span>\n        </h1>\n        <p class=\"hero-tagline\">{{ content.tagline }}</p>\n\n        <div class=\"hero-actions\">\n          <a :href=\"startLink\" class=\"btn-primary\">\n            {{ content.startButton }}\n            <span class=\"arrow\">-></span>\n          </a>\n          <a\n            href=\"https://v3.vuejs.org/\"\n            class=\"btn-secondary\"\n            target=\"_blank\"\n            rel=\"noopener\"\n          >\n            {{ content.vueButton }}\n          </a>\n        </div>\n      </div>\n\n      <div class=\"hero-visual\">\n        <div class=\"stairs-container\">\n          <div class=\"stair stair-1\"></div>\n          <div class=\"stair stair-2\"></div>\n          <div class=\"stair stair-3\"></div>\n          <div class=\"stair stair-4\"></div>\n          <div class=\"stair stair-5\"></div>\n\n          <div class=\"mascot-container\">\n            <img\n              src=\"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko.png\"\n              alt=\"Kawaiko - chibivue mascot\"\n              class=\"mascot\"\n              loading=\"eager\"\n            />\n          </div>\n        </div>\n\n        <div class=\"code-snippets\">\n          <div class=\"snippet snippet-1\">\n            <code>const app = createApp()</code>\n          </div>\n          <div class=\"snippet snippet-2\">\n            <code>ref() reactive()</code>\n          </div>\n          <div class=\"snippet snippet-3\">\n            <code>&lt;template&gt;</code>\n          </div>\n        </div>\n      </div>\n    </div>\n  </section>\n</template>\n\n<style scoped>\n.hero-section {\n  position: relative;\n  min-height: 80vh;\n  display: flex;\n  align-items: center;\n  overflow: hidden;\n  padding: 4rem 2rem;\n}\n\n.hero-background {\n  position: absolute;\n  inset: 0;\n  z-index: 0;\n}\n\n.code-pattern {\n  position: absolute;\n  inset: 0;\n  background-image: radial-gradient(\n      circle at 20% 50%,\n      rgba(26, 179, 148, 0.1) 0%,\n      transparent 50%\n    ),\n    radial-gradient(\n      circle at 80% 20%,\n      rgba(44, 201, 168, 0.08) 0%,\n      transparent 40%\n    );\n  opacity: 0.6;\n}\n\n.gradient-overlay {\n  position: absolute;\n  inset: 0;\n  background: linear-gradient(135deg, #0f1724 0%, #0d121b 100%);\n  opacity: 0.95;\n}\n\n.hero-content {\n  position: relative;\n  z-index: 1;\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 3rem;\n  max-width: 1200px;\n  margin: 0 auto;\n  width: 100%;\n}\n\n@media (min-width: 960px) {\n  .hero-content {\n    grid-template-columns: 1fr 1fr;\n    gap: 4rem;\n  }\n}\n\n.hero-text {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  text-align: center;\n}\n\n@media (min-width: 960px) {\n  .hero-text {\n    text-align: left;\n  }\n}\n\n.hero-title {\n  margin: 0;\n  line-height: 1.1;\n}\n\n.gradient-text {\n  background: linear-gradient(\n    135deg,\n    var(--c-mint-500, #1ab394) 0%,\n    var(--c-mint-400, #2cc9a8) 50%,\n    #5dddc2 100%\n  );\n  -webkit-background-clip: text;\n  background-clip: text;\n  -webkit-text-fill-color: transparent;\n  font-size: clamp(3rem, 10vw, 5rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n}\n\n.version-badge {\n  display: inline-block;\n  vertical-align: super;\n  font-size: clamp(0.75rem, 2vw, 1rem);\n  font-weight: 600;\n  padding: 0.25em 0.6em;\n  margin-left: 0.5rem;\n  background: rgba(26, 179, 148, 0.15);\n  border: 1px solid rgba(26, 179, 148, 0.4);\n  border-radius: 6px;\n  color: var(--c-mint-400, #2cc9a8);\n}\n\n.hero-tagline {\n  font-size: clamp(1rem, 2.5vw, 1.25rem);\n  color: var(--vp-c-text-2);\n  margin: 1.5rem 0 2rem;\n  line-height: 1.7;\n  max-width: 500px;\n}\n\n@media (min-width: 960px) {\n  .hero-tagline {\n    margin: 1.5rem 0 2.5rem;\n  }\n}\n\n@media (max-width: 959px) {\n  .hero-tagline {\n    margin-left: auto;\n    margin-right: auto;\n  }\n}\n\n.hero-actions {\n  display: flex;\n  gap: 1rem;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n@media (min-width: 960px) {\n  .hero-actions {\n    justify-content: flex-start;\n  }\n}\n\n.btn-primary,\n.btn-secondary {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.5rem;\n  padding: 0.875rem 1.75rem;\n  border-radius: 8px;\n  font-weight: 600;\n  font-size: 0.95rem;\n  text-decoration: none;\n  transition: all 0.25s ease;\n}\n\n.btn-primary {\n  background: var(--c-mint-500, #1ab394);\n  color: #ffffff;\n  border: 2px solid transparent;\n}\n\n.btn-primary:hover {\n  background: var(--c-mint-600, #159d82);\n  transform: translateY(-2px);\n  box-shadow: 0 8px 20px rgba(26, 179, 148, 0.3);\n}\n\n.btn-primary .arrow {\n  transition: transform 0.2s ease;\n}\n\n.btn-primary:hover .arrow {\n  transform: translateX(4px);\n}\n\n.btn-secondary {\n  background: transparent;\n  color: var(--vp-c-text-1);\n  border: 2px solid var(--vp-c-border);\n}\n\n.btn-secondary:hover {\n  border-color: var(--c-mint-400, #2cc9a8);\n  color: var(--c-mint-500, #1ab394);\n}\n\n/* Hero Visual - Stairs */\n.hero-visual {\n  position: relative;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  order: -1;\n}\n\n@media (min-width: 960px) {\n  .hero-visual {\n    order: 0;\n  }\n}\n\n.stairs-container {\n  position: relative;\n  width: 100%;\n  max-width: 350px;\n  height: 300px;\n}\n\n@media (min-width: 640px) {\n  .stairs-container {\n    max-width: 400px;\n    height: 350px;\n  }\n}\n\n.stair {\n  position: absolute;\n  width: 100px;\n  height: 24px;\n  background: linear-gradient(\n    180deg,\n    var(--c-mint-400, #2cc9a8) 0%,\n    var(--c-mint-500, #1ab394) 100%\n  );\n  border-radius: 4px;\n  box-shadow:\n    0 4px 20px rgba(26, 179, 148, 0.3),\n    inset 0 1px 0 rgba(255, 255, 255, 0.2);\n  animation: stairAppear 0.6s ease-out forwards;\n  opacity: 0;\n}\n\n@media (min-width: 640px) {\n  .stair {\n    width: 120px;\n    height: 30px;\n  }\n}\n\n.stair-1 {\n  bottom: 20px;\n  left: 10%;\n  animation-delay: 0.1s;\n}\n.stair-2 {\n  bottom: 60px;\n  left: 20%;\n  animation-delay: 0.2s;\n}\n.stair-3 {\n  bottom: 100px;\n  left: 30%;\n  animation-delay: 0.3s;\n}\n.stair-4 {\n  bottom: 140px;\n  left: 40%;\n  animation-delay: 0.4s;\n}\n.stair-5 {\n  bottom: 180px;\n  left: 50%;\n  animation-delay: 0.5s;\n}\n\n@media (min-width: 640px) {\n  .stair-1 {\n    bottom: 20px;\n    left: 0;\n  }\n  .stair-2 {\n    bottom: 70px;\n    left: 40px;\n  }\n  .stair-3 {\n    bottom: 120px;\n    left: 80px;\n  }\n  .stair-4 {\n    bottom: 170px;\n    left: 120px;\n  }\n  .stair-5 {\n    bottom: 220px;\n    left: 160px;\n  }\n}\n\n@keyframes stairAppear {\n  0% {\n    opacity: 0;\n    transform: translateY(20px) rotateX(-20deg);\n  }\n  100% {\n    opacity: 1;\n    transform: translateY(0) rotateX(0);\n  }\n}\n\n/* Mascot */\n.mascot-container {\n  position: absolute;\n  bottom: 190px;\n  left: 50%;\n  transform: translateX(-50%);\n  animation: mascotBounce 2s ease-in-out infinite;\n  animation-delay: 0.6s;\n}\n\n@media (min-width: 640px) {\n  .mascot-container {\n    bottom: 230px;\n    left: 140px;\n    transform: none;\n  }\n}\n\n.mascot {\n  width: 80px;\n  height: auto;\n  filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.3));\n  transition: transform 0.3s ease;\n}\n\n@media (min-width: 640px) {\n  .mascot {\n    width: 100px;\n  }\n}\n\n.mascot:hover {\n  transform: scale(1.05) rotate(-5deg);\n}\n\n@keyframes mascotBounce {\n  0%,\n  100% {\n    transform: translateX(-50%) translateY(0);\n  }\n  50% {\n    transform: translateX(-50%) translateY(-8px);\n  }\n}\n\n@media (min-width: 640px) {\n  @keyframes mascotBounce {\n    0%,\n    100% {\n      transform: translateY(0);\n    }\n    50% {\n      transform: translateY(-8px);\n    }\n  }\n}\n\n/* Code Snippets */\n.code-snippets {\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n}\n\n.snippet {\n  position: absolute;\n  padding: 0.5rem 1rem;\n  background: rgba(26, 39, 68, 0.8);\n  border: 1px solid rgba(26, 179, 148, 0.3);\n  border-radius: 6px;\n  font-family: 'Fira Code', 'Consolas', monospace;\n  font-size: 0.75rem;\n  color: var(--c-mint-400, #2cc9a8);\n  animation: float 4s ease-in-out infinite;\n  backdrop-filter: blur(4px);\n  white-space: nowrap;\n  display: none;\n}\n\n@media (min-width: 640px) {\n  .snippet {\n    display: block;\n    font-size: 0.85rem;\n  }\n}\n\n.snippet-1 {\n  top: 0;\n  left: 0;\n  animation-delay: 0s;\n}\n.snippet-2 {\n  top: 30%;\n  right: 0;\n  animation-delay: 1.3s;\n}\n.snippet-3 {\n  bottom: 10%;\n  left: 0;\n  animation-delay: 2.6s;\n}\n\n@media (min-width: 640px) {\n  .snippet-1 {\n    top: 5%;\n    left: auto;\n    right: 0;\n  }\n  .snippet-2 {\n    top: 35%;\n    right: -5%;\n  }\n  .snippet-3 {\n    bottom: 25%;\n    left: auto;\n    right: 5%;\n  }\n}\n\n@keyframes float {\n  0%,\n  100% {\n    transform: translateY(0) rotate(0deg);\n  }\n  50% {\n    transform: translateY(-10px) rotate(1deg);\n  }\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .stair {\n    animation: none;\n    opacity: 1;\n    transform: none;\n  }\n\n  .mascot-container {\n    animation: none;\n  }\n\n  .snippet {\n    animation: none;\n  }\n}\n</style>\n\n<style>\n/* Light mode overrides - unscoped for proper cascade */\nhtml:not(.dark) .hero-section .gradient-overlay {\n  background: linear-gradient(135deg, #f8faf9 0%, #edf4f2 100%) !important;\n  opacity: 1 !important;\n}\n\nhtml:not(.dark) .hero-section .code-pattern {\n  background-image: radial-gradient(\n      circle at 20% 50%,\n      rgba(26, 179, 148, 0.12) 0%,\n      transparent 50%\n    ),\n    radial-gradient(\n      circle at 80% 20%,\n      rgba(44, 201, 168, 0.1) 0%,\n      transparent 40%\n    ) !important;\n}\n\nhtml:not(.dark) .hero-section .hero-tagline {\n  color: #3d4f5f !important;\n}\n\nhtml:not(.dark) .hero-section .btn-secondary {\n  background: #ffffff !important;\n  border-color: var(--c-mint-400, #2cc9a8) !important;\n  color: #1a2744 !important;\n}\n\nhtml:not(.dark) .hero-section .btn-secondary:hover {\n  background: #e8f7f4 !important;\n  color: #1a2744 !important;\n}\n\nhtml:not(.dark) .hero-section .snippet {\n  background: rgba(255, 255, 255, 0.95) !important;\n  border-color: rgba(26, 179, 148, 0.35) !important;\n  box-shadow: 0 4px 16px rgba(26, 179, 148, 0.12) !important;\n  color: #1ab394 !important;\n}\n\nhtml:not(.dark) .hero-section .version-badge {\n  background: rgba(26, 179, 148, 0.1) !important;\n  border-color: rgba(26, 179, 148, 0.5) !important;\n  color: #1ab394 !important;\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/KawaikoNote.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\ninterface Props {\n  variant?: \"base\" | \"angry\" | \"funny\" | \"question\" | \"surprise\" | \"warning\";\n  type?: \"info\" | \"tip\" | \"warning\" | \"danger\" | \"success\";\n  title?: string;\n  position?: \"left\" | \"right\";\n  size?: \"sm\" | \"md\" | \"lg\";\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  variant: \"base\",\n  type: \"info\",\n  position: \"left\",\n  size: \"md\",\n});\n\nconst mascotImages: Record<string, string> = {\n  base: \"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko.png\",\n  angry:\n    \"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_angry.png\",\n  funny:\n    \"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_funny.png\",\n  question:\n    \"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_question.png\",\n  surprise:\n    \"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_surprise.png\",\n  warning:\n    \"https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_warning.png\",\n};\n\nconst typeToVariantMap: Record<string, string> = {\n  warning: \"warning\",\n  danger: \"angry\",\n  tip: \"funny\",\n  success: \"base\",\n  info: \"question\",\n};\n\nconst effectiveVariant = computed(() => {\n  if (props.variant !== \"base\") return props.variant;\n  return typeToVariantMap[props.type] || \"base\";\n});\n\nconst mascotSrc = computed(() => mascotImages[effectiveVariant.value]);\n</script>\n\n<template>\n  <div\n    class=\"kawaiko-note\"\n    :class=\"[\n      `kawaiko-note--${size}`,\n      `kawaiko-note--${type}`,\n      `kawaiko-note--${position}`,\n    ]\"\n  >\n    <div class=\"kawaiko-note__mascot\">\n      <img\n        :src=\"mascotSrc\"\n        :alt=\"`Kawaiko mascot - ${effectiveVariant}`\"\n        class=\"kawaiko-note__image\"\n        loading=\"lazy\"\n      />\n    </div>\n    <div class=\"kawaiko-note__content\">\n      <div v-if=\"title\" class=\"kawaiko-note__title\">\n        {{ title }}\n      </div>\n      <div class=\"kawaiko-note__body\">\n        <slot />\n      </div>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.kawaiko-note {\n  display: flex;\n  align-items: flex-start;\n  gap: 16px;\n  padding: 16px 20px;\n  margin: 16px 0;\n  border-radius: 12px;\n  border: 1px solid var(--vp-c-border);\n  background: var(--vp-c-bg-soft);\n  transition: all 0.25s ease;\n}\n\n.kawaiko-note:hover {\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);\n}\n\nhtml.dark .kawaiko-note:hover {\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n}\n\n/* Positions */\n.kawaiko-note--right {\n  flex-direction: row-reverse;\n}\n\n/* Mascot Image */\n.kawaiko-note__mascot {\n  flex-shrink: 0;\n}\n\n.kawaiko-note__image {\n  display: block;\n  border-radius: 8px;\n  object-fit: contain;\n}\n\n/* Sizes */\n.kawaiko-note--sm .kawaiko-note__image {\n  width: 48px;\n  height: 48px;\n}\n\n.kawaiko-note--md .kawaiko-note__image {\n  width: 72px;\n  height: 72px;\n}\n\n.kawaiko-note--lg .kawaiko-note__image {\n  width: 96px;\n  height: 96px;\n}\n\n/* Content */\n.kawaiko-note__content {\n  flex: 1;\n  min-width: 0;\n}\n\n.kawaiko-note__title {\n  font-weight: 600;\n  font-size: 1rem;\n  margin-bottom: 8px;\n  color: var(--vp-c-text-1);\n}\n\n.kawaiko-note__body {\n  font-size: 0.95rem;\n  line-height: 1.6;\n  color: var(--vp-c-text-2);\n}\n\n.kawaiko-note__body :deep(p) {\n  margin: 0;\n}\n\n.kawaiko-note__body :deep(p + p) {\n  margin-top: 8px;\n}\n\n.kawaiko-note__body :deep(code) {\n  background: var(--vp-c-bg-mute);\n  padding: 2px 6px;\n  border-radius: 4px;\n  font-size: 0.9em;\n}\n\n/* Type Variants */\n.kawaiko-note--info {\n  border-color: var(--c-mint-300);\n  background: rgba(26, 179, 148, 0.06);\n}\n\n.kawaiko-note--tip {\n  border-color: var(--c-mint-400);\n  background: rgba(44, 201, 168, 0.08);\n}\n\n.kawaiko-note--success {\n  border-color: var(--c-mint-500);\n  background: rgba(26, 179, 148, 0.1);\n}\n\n.kawaiko-note--warning {\n  border-color: var(--c-duck-yellow);\n  background: rgba(244, 211, 94, 0.1);\n}\n\n.kawaiko-note--danger {\n  border-color: #e85d75;\n  background: rgba(232, 93, 117, 0.08);\n}\n\n/* Dark mode adjustments */\n:global(html.dark) .kawaiko-note--info {\n  background: rgba(26, 179, 148, 0.08);\n}\n\n:global(html.dark) .kawaiko-note--tip {\n  background: rgba(44, 201, 168, 0.1);\n}\n\n:global(html.dark) .kawaiko-note--success {\n  background: rgba(26, 179, 148, 0.12);\n}\n\n:global(html.dark) .kawaiko-note--warning {\n  background: rgba(244, 211, 94, 0.12);\n}\n\n:global(html.dark) .kawaiko-note--danger {\n  background: rgba(232, 93, 117, 0.1);\n}\n\n/* Responsive */\n@media (max-width: 640px) {\n  .kawaiko-note {\n    padding: 12px 16px;\n    gap: 12px;\n  }\n\n  .kawaiko-note--md .kawaiko-note__image,\n  .kawaiko-note--lg .kawaiko-note__image {\n    width: 56px;\n    height: 56px;\n  }\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/SidebarEnhancer.vue",
    "content": "<script setup lang=\"ts\">\nimport { onMounted, watch } from 'vue'\nimport { useRoute } from 'vitepress'\n\nconst route = useRoute()\n\nconst KAWAIKO_WARNING =\n  'https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_warning.png'\nconst KAWAIKO_SURPRISE =\n  'https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_surprise.png'\n\n// Chapters that are outdated and need surprise warning\nconst outdatedChapters = ['template-compiler', 'reactivity-system']\n\nfunction enhanceSidebar() {\n  // Replace 🚧 with kawaiko_warning image at the end\n  const sidebarItems = document.querySelectorAll('.VPSidebar .text')\n\n  sidebarItems.forEach(item => {\n    const text = item.textContent || ''\n\n    // Move 🚧 emoji to the end as an image\n    if (text.includes('🚧') && !item.querySelector('.kawaiko-sidebar-icon')) {\n      // Remove the 🚧 emoji from text\n      item.innerHTML = item.innerHTML.replace('🚧', '').trim()\n\n      // Append image at the end\n      const img = document.createElement('img')\n      img.src = KAWAIKO_WARNING\n      img.alt = 'WIP'\n      img.className = 'kawaiko-sidebar-icon'\n      img.style.cssText =\n        'width: 18px; height: 18px; display: inline-block; vertical-align: middle; margin-left: 6px;'\n\n      item.appendChild(img)\n    }\n  })\n\n  // Add subtle outdated indicator to section headers (text-based, not image)\n  const sectionHeaders = document.querySelectorAll(\n    '.VPSidebar .VPSidebarItem.level-0 > .item',\n  )\n\n  sectionHeaders.forEach(header => {\n    const textEl = header.querySelector('.text')\n    if (!textEl) return\n\n    const text = textEl.textContent || ''\n    const isOutdated = outdatedChapters.some(\n      chapter =>\n        text.toLowerCase().includes(chapter.toLowerCase()) &&\n        !text.includes('🚧'),\n    )\n\n    if (isOutdated && !header.classList.contains('outdated-section')) {\n      header.classList.add('outdated-section')\n    }\n  })\n}\n\nonMounted(() => {\n  // Initial enhancement\n  setTimeout(enhanceSidebar, 100)\n\n  // Re-enhance on route change\n  watch(\n    () => route.path,\n    () => {\n      setTimeout(enhanceSidebar, 100)\n    },\n  )\n\n  // Also observe DOM changes for dynamic sidebar updates\n  const observer = new MutationObserver(() => {\n    enhanceSidebar()\n  })\n\n  const sidebar = document.querySelector('.VPSidebar')\n  if (sidebar) {\n    observer.observe(sidebar, { childList: true, subtree: true })\n  }\n})\n</script>\n\n<template>\n  <div class=\"sidebar-enhancer\"></div>\n</template>\n\n<style>\n.kawaiko-sidebar-icon {\n  transition: transform 0.2s ease;\n}\n\n.kawaiko-sidebar-icon:hover {\n  transform: scale(1.2);\n}\n\n/* Outdated section styling */\n.outdated-section .text::after {\n  content: ' (outdated)';\n  font-size: 0.75em;\n  color: var(--c-duck-orange, #e8a545);\n  font-weight: 400;\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/components/WipBanner.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useData } from 'vitepress'\n\nconst { page, frontmatter, lang } = useData()\n\nconst KAWAIKO_WARNING =\n  'https://raw.githubusercontent.com/chibivue-land/art/main/kawaiko_warning.png'\n\nconst i18n: Record<string, { label: string; message: string }> = {\n  en: {\n    label: 'Work in Progress',\n    message: 'This page is under construction. Content may change.',\n  },\n  ja: {\n    label: '準備中',\n    message: 'このページは準備中です。内容が変更される可能性があります。',\n  },\n  'zh-cn': {\n    label: '正在施工',\n    message: '此页面正在建设中，内容可能会有所变更。',\n  },\n  'zh-tw': {\n    label: '正在施工',\n    message: '此頁面正在建設中，內容可能會有所變更。',\n  },\n}\n\nconst currentI18n = computed(() => i18n[lang.value] || i18n.en)\n\n// Check if page is WIP based on frontmatter or title containing 🚧 or \"WIP\"\nconst isWip = computed(() => {\n  if (frontmatter.value.wip === true) return true\n  const title = page.value.title || ''\n  return title.includes('🚧') || title.includes('WIP')\n})\n\n// Get custom message from frontmatter or use default\nconst wipMessage = computed(() => {\n  if (typeof frontmatter.value.wip === 'string') {\n    return frontmatter.value.wip\n  }\n  return currentI18n.value.message\n})\n\nconst wipLabel = computed(() => currentI18n.value.label)\n</script>\n\n<template>\n  <div v-if=\"isWip\" class=\"wip-banner\">\n    <div class=\"wip-banner__content\">\n      <img\n        :src=\"KAWAIKO_WARNING\"\n        alt=\"Kawaiko Warning\"\n        class=\"wip-banner__mascot\"\n        loading=\"eager\"\n      />\n      <div class=\"wip-banner__text\">\n        <span class=\"wip-banner__label\">{{ wipLabel }}</span>\n        <p class=\"wip-banner__message\">{{ wipMessage }}</p>\n      </div>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n.wip-banner {\n  margin: 0 0 24px 0;\n  padding: 16px 20px;\n  background: linear-gradient(\n    135deg,\n    rgba(244, 211, 94, 0.12) 0%,\n    rgba(232, 165, 69, 0.08) 100%\n  );\n  border: 1px solid var(--c-duck-yellow, #f4d35e);\n  border-radius: 12px;\n  border-left: 4px solid var(--c-duck-orange, #e8a545);\n}\n\n.wip-banner__content {\n  display: flex;\n  align-items: center;\n  gap: 16px;\n}\n\n.wip-banner__mascot {\n  width: 56px;\n  height: 56px;\n  flex-shrink: 0;\n  animation: wiggle 2s ease-in-out infinite;\n}\n\n@keyframes wiggle {\n  0%,\n  100% {\n    transform: rotate(0deg);\n  }\n  25% {\n    transform: rotate(-5deg);\n  }\n  75% {\n    transform: rotate(5deg);\n  }\n}\n\n.wip-banner__text {\n  flex: 1;\n}\n\n.wip-banner__label {\n  display: inline-block;\n  font-weight: 700;\n  font-size: 0.85rem;\n  color: var(--c-duck-orange, #e8a545);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  margin-bottom: 4px;\n}\n\n.wip-banner__message {\n  margin: 0;\n  font-size: 0.95rem;\n  color: var(--vp-c-text-2);\n  line-height: 1.5;\n}\n\n/* Dark mode */\nhtml.dark .wip-banner {\n  background: linear-gradient(\n    135deg,\n    rgba(244, 211, 94, 0.1) 0%,\n    rgba(232, 165, 69, 0.06) 100%\n  );\n}\n\n/* Responsive */\n@media (max-width: 640px) {\n  .wip-banner {\n    padding: 12px 16px;\n  }\n\n  .wip-banner__content {\n    gap: 12px;\n  }\n\n  .wip-banner__mascot {\n    width: 44px;\n    height: 44px;\n  }\n\n  .wip-banner__label {\n    font-size: 0.8rem;\n  }\n\n  .wip-banner__message {\n    font-size: 0.9rem;\n  }\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .wip-banner__mascot {\n    animation: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/index.ts",
    "content": "import { inBrowser, useData } from \"vitepress\";\nimport DefaultTheme from \"vitepress/theme-without-fonts\";\nimport Layout from \"./Layout.vue\";\nimport KawaikoNote from \"./components/KawaikoNote.vue\";\nimport \"./main.css\";\n\nexport default {\n  extends: DefaultTheme,\n  Layout,\n  enhanceApp({ app }) {\n    app.component(\"KawaikoNote\", KawaikoNote);\n  },\n  setup() {\n    const { lang } = useData();\n    if (inBrowser) {\n      document.cookie = `nf_lang=${lang.value}; expires=Mon, 1 Jan 2030 00:00:00 UTC; path=/`;\n    }\n  },\n};\n"
  },
  {
    "path": "book/online-book/.vitepress/theme/main.css",
    "content": "/**\n * chibivue Theme Colors - New Branding\n * Based on og.png design: Mint/Turquoise + Navy\n * -------------------------------------------------------------------------- */\n\n:root {\n  /* Primary Brand Colors - Mint/Turquoise */\n  --c-mint-50: #e8faf6;\n  --c-mint-100: #c5f2e8;\n  --c-mint-200: #8be4d3;\n  --c-mint-300: #50d6bd;\n  --c-mint-400: #2cc9a8;\n  --c-mint-500: #1ab394;\n  --c-mint-600: #159d82;\n  --c-mint-700: #0f8770;\n  --c-mint-800: #0a715e;\n  --c-mint-900: #055c4c;\n\n  /* Navy Text Colors */\n  --c-navy-900: #1a2744;\n  --c-navy-800: #243352;\n  --c-navy-700: #2e3f60;\n  --c-navy-600: #3a4d72;\n  --c-navy-500: #4a5f87;\n  --c-navy-400: #6b7fa3;\n  --c-navy-300: #9aaac4;\n  --c-navy-200: #c4d0e1;\n  --c-navy-100: #e4eaf2;\n\n  /* Mascot Colors */\n  --c-duck-yellow: #f4d35e;\n  --c-duck-orange: #e8a545;\n  --c-duck-brown: #8b6914;\n\n  /* Legacy color mappings for compatibility */\n  --c-blue: var(--c-mint-500);\n  --c-blue-dark: var(--c-navy-800);\n  --c-blue-darker: var(--c-navy-900);\n  --c-blue-light: var(--c-mint-400);\n  --c-blue-lighter: var(--c-mint-300);\n  --c-teal: var(--c-mint-700);\n  --c-teal-light: var(--c-mint-600);\n  --c-green-light: #8ae99c;\n  --c-green: #52ce63;\n  --c-green-dark: #51a256;\n  --c-green-darker: #316334;\n\n  /* VitePress Brand Variables - Light Mode */\n  --vp-c-brand-1: var(--c-mint-600);\n  --vp-c-brand-2: var(--c-mint-500);\n  --vp-c-brand-3: var(--c-mint-400);\n  --vp-c-brand-soft: rgba(26, 179, 148, 0.14);\n\n  /* Text Colors - Light Mode */\n  --vp-c-text-1: var(--c-navy-900);\n  --vp-c-text-2: var(--c-navy-700);\n  --vp-c-text-3: var(--c-navy-500);\n  --c-text-light-1: var(--c-navy-900);\n  --c-text-light-2: var(--c-navy-700);\n  --c-text-light-3: var(--c-navy-500);\n\n  /* Background Colors - Light Mode */\n  --c-white-dark: #f8fafb;\n  --vp-c-bg: #ffffff;\n  --vp-c-bg-soft: var(--c-mint-50);\n  --vp-c-bg-mute: var(--c-navy-100);\n  --vp-c-bg-alt: #f8fafb;\n\n  /* Border Colors */\n  --vp-c-border: var(--c-navy-200);\n  --vp-c-divider: var(--c-navy-100);\n\n  /* Gradient for hero - Light Mode */\n  --vp-home-hero-name-color: transparent;\n  --vp-home-hero-name-background: linear-gradient(\n    135deg,\n    var(--c-mint-600) 0%,\n    var(--c-mint-400) 50%,\n    var(--c-green-light) 100%\n  );\n  --vp-home-hero-image-background-image: linear-gradient(\n    -45deg,\n    var(--c-mint-300) 0%,\n    var(--c-mint-100) 50%,\n    rgba(26, 179, 148, 0.3) 100%\n  );\n  --vp-home-hero-image-filter: blur(44px);\n}\n\n/**\n * Dark Mode\n * -------------------------------------------------------------------------- */\n\nhtml.dark {\n  /* VitePress Brand Variables - Dark Mode */\n  --vp-c-brand-1: var(--c-mint-400);\n  --vp-c-brand-2: var(--c-mint-500);\n  --vp-c-brand-3: var(--c-mint-600);\n  --vp-c-brand-soft: rgba(44, 201, 168, 0.16);\n\n  /* Text Colors - Dark Mode */\n  --vp-c-text-1: #e2ebf0;\n  --vp-c-text-2: #b8c9d4;\n  --vp-c-text-3: #8a9fb0;\n  --vp-c-text-dark-1: #e2ebf0;\n  --vp-c-text-dark-2: #b8c9d4;\n  --vp-c-text-dark-3: #8a9fb0;\n\n  /* Background Colors - Dark Mode */\n  --vp-c-black-darker: #0d121b;\n  --vp-c-black: #0f1724;\n  --vp-c-black-light: #151e2d;\n  --vp-c-black-lighter: #1b2637;\n  --vp-c-bg: #0f1724;\n  --vp-c-bg-soft: #151e2d;\n  --vp-c-bg-mute: #1b2637;\n  --vp-c-bg-alt: #0d121b;\n  --vp-c-bg-elv: var(--vp-c-bg-soft);\n  --vp-c-bg-elv-mute: var(--vp-c-bg-mute);\n  --vp-c-bg-soft-up: var(--vp-c-black-lighter);\n  --vp-c-bg-soft-mute: var(--vp-c-black-lighter);\n  --vp-c-mute: var(--vp-c-bg-mute);\n  --vp-c-mute-dark: var(--vp-c-black-lighter);\n  --vp-c-mute-darker: var(--vp-c-black-darker);\n  --c-bg-accent: var(--vp-c-black-light);\n\n  /* Border Colors - Dark Mode */\n  --vp-c-border: #2a3a50;\n  --vp-c-divider: #1f2d40;\n\n  /* Hero gradient - Dark Mode */\n  --vp-home-hero-name-background: linear-gradient(\n    135deg,\n    var(--c-mint-400) 0%,\n    var(--c-mint-300) 50%,\n    #5dddc2 100%\n  );\n\n  --vp-c-bg-alpha-with-backdrop: rgba(15, 23, 36, 0.7);\n  --vp-c-bg-alpha-without-backdrop: rgba(15, 23, 36, 0.9);\n\n  --vp-code-line-highlight-color: rgba(26, 179, 148, 0.1);\n}\n\n/**\n * Component: Button\n * -------------------------------------------------------------------------- */\n\n:root {\n  --vp-button-brand-border: transparent;\n  --vp-button-brand-text: #ffffff;\n  --vp-button-brand-bg: var(--c-mint-600);\n  --vp-button-brand-hover-border: transparent;\n  --vp-button-brand-hover-text: #ffffff;\n  --vp-button-brand-hover-bg: var(--c-mint-700);\n  --vp-button-brand-active-border: transparent;\n  --vp-button-brand-active-text: #ffffff;\n  --vp-button-brand-active-bg: var(--c-mint-800);\n}\n\nhtml.dark {\n  --vp-button-brand-bg: var(--c-mint-500);\n  --vp-button-brand-hover-bg: var(--c-mint-600);\n  --vp-button-brand-active-bg: var(--c-mint-700);\n}\n\n/**\n * Component: Custom Containers (tip, warning, info, etc.)\n * -------------------------------------------------------------------------- */\n\n:root {\n  /* Tip - use mint */\n  --vp-custom-block-tip-border: var(--c-mint-300);\n  --vp-custom-block-tip-text: var(--c-navy-800);\n  --vp-custom-block-tip-bg: rgba(26, 179, 148, 0.1);\n  --vp-custom-block-tip-code-bg: rgba(26, 179, 148, 0.15);\n\n  /* Info - use teal */\n  --vp-custom-block-info-border: var(--c-mint-400);\n  --vp-custom-block-info-text: var(--c-navy-800);\n  --vp-custom-block-info-bg: rgba(44, 201, 168, 0.1);\n\n  /* Warning - use duck yellow */\n  --vp-custom-block-warning-border: var(--c-duck-yellow);\n  --vp-custom-block-warning-text: var(--c-navy-800);\n  --vp-custom-block-warning-bg: rgba(244, 211, 94, 0.12);\n\n  /* Danger - softer red */\n  --vp-custom-block-danger-border: #e85d75;\n  --vp-custom-block-danger-text: var(--c-navy-800);\n  --vp-custom-block-danger-bg: rgba(232, 93, 117, 0.1);\n}\n\nhtml.dark {\n  --vp-custom-block-tip-text: #e2ebf0;\n  --vp-custom-block-info-text: #e2ebf0;\n  --vp-custom-block-warning-text: #e2ebf0;\n  --vp-custom-block-danger-text: #e2ebf0;\n}\n\n/**\n * Component: Home\n * -------------------------------------------------------------------------- */\n\n.VPHero .VPImage.image-src {\n  max-height: 192px;\n}\n\n@media (min-width: 640px) {\n  :root {\n    --vp-home-hero-image-filter: blur(56px);\n  }\n  .VPHero .VPImage.image-src {\n    max-height: 256px;\n  }\n}\n\n@media (min-width: 960px) {\n  :root {\n    --vp-home-hero-image-filter: blur(72px);\n  }\n  .VPHero .VPImage.image-src {\n    max-height: 320px;\n  }\n}\n\n/**\n * Component: Code Blocks - Retro Terminal Style\n * -------------------------------------------------------------------------- */\n\n/* Light Mode - Paper/vintage terminal style */\n:root {\n  --vp-code-block-bg: #f5f5f0;\n  --vp-code-block-divider-color: #d0d8d5;\n  --vp-code-line-diff-add-color: rgba(26, 138, 110, 0.4);\n  --vp-code-line-diff-remove-color: rgba(192, 64, 80, 0.3);\n  --vp-code-line-highlight-color: rgba(26, 138, 110, 0.2);\n  --vp-code-copy-code-hover-bg: rgba(26, 138, 110, 0.2);\n  --vp-code-color: #1a5c4a;\n}\n\n/* Dark Mode - Terminal style */\nhtml.dark {\n  --vp-code-block-bg: #0a0f14;\n  --vp-code-block-divider-color: #1a2744;\n  --vp-code-line-diff-add-color: rgba(44, 201, 168, 0.35);\n  --vp-code-line-diff-remove-color: rgba(232, 93, 117, 0.28);\n  --vp-code-line-highlight-color: rgba(44, 201, 168, 0.2);\n  --vp-code-copy-code-hover-bg: rgba(44, 201, 168, 0.3);\n  --vp-code-color: #8be4d3;\n}\n\n/* Code block frame - Light Mode */\ndiv[class^='language-'],\ndiv[class*=' language-'] {\n  border: 1px solid #d0d8d5;\n  box-shadow: 0 2px 8px rgba(26, 92, 74, 0.08);\n}\n\n/* Code block frame - Dark Mode */\nhtml.dark div[class^='language-'],\nhtml.dark div[class*=' language-'] {\n  border: 1px solid #1a2744;\n  box-shadow: 0 0 20px rgba(44, 201, 168, 0.1), inset 0 0 60px rgba(0, 0, 0, 0.3);\n}\n\n/* Scanline effect - Dark Mode only */\nhtml.dark div[class^='language-']::before,\nhtml.dark div[class*=' language-']::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: repeating-linear-gradient(\n    0deg,\n    rgba(0, 0, 0, 0.05),\n    rgba(0, 0, 0, 0.05) 1px,\n    transparent 1px,\n    transparent 2px\n  );\n  pointer-events: none;\n  z-index: 1;\n}\n\n/* Language label styling - Light Mode */\ndiv[class^='language-'] > span.lang,\ndiv[class*=' language-'] > span.lang {\n  color: #1a8a6e;\n  font-weight: 600;\n  text-transform: uppercase;\n  font-size: 0.7em;\n  letter-spacing: 0.1em;\n  opacity: 0.8;\n}\n\n/* Language label styling - Dark Mode */\nhtml.dark div[class^='language-'] > span.lang,\nhtml.dark div[class*=' language-'] > span.lang {\n  color: #2cc9a8;\n}\n\n/* Line numbers - Light Mode */\ndiv[class^='language-'] .line-numbers,\ndiv[class*=' language-'] .line-numbers {\n  color: #7a9a8a;\n  border-right: 1px solid #d0d8d5;\n}\n\n/* Line numbers - Dark Mode */\nhtml.dark div[class^='language-'] .line-numbers,\nhtml.dark div[class*=' language-'] .line-numbers {\n  color: #4a5f87;\n  border-right: 1px solid #1a2744;\n}\n\n/* Inline code - Light Mode */\n:not(pre) > code {\n  background-color: #f0f5f3 !important;\n  color: #1a5c4a !important;\n  border: 1px solid #d0d8d5;\n  padding: 0.15em 0.4em;\n  border-radius: 3px;\n  font-size: 0.9em;\n}\n\n/* Inline code - Dark Mode */\nhtml.dark :not(pre) > code {\n  background-color: #0a0f14 !important;\n  color: #8be4d3 !important;\n  border: 1px solid #1a2744;\n}\n\n/**\n * Component: Layout - Wide Display Support\n * -------------------------------------------------------------------------- */\n\n/* Wide display support */\n@media (min-width: 1600px) {\n  :root {\n    --vp-layout-max-width: 1600px;\n  }\n}\n\n@media (min-width: 1920px) {\n  :root {\n    --vp-layout-max-width: 1800px;\n  }\n}\n\n/* Content area max-width for readability on wide displays */\n@media (min-width: 1440px) {\n  .VPDoc:not(.has-aside) .content-container {\n    max-width: 900px;\n  }\n\n  .VPDoc.has-aside .content-container {\n    max-width: 768px;\n  }\n}\n\n@media (min-width: 1600px) {\n  .VPDoc:not(.has-aside) .content-container {\n    max-width: 960px;\n  }\n\n  .VPDoc.has-aside .content-container {\n    max-width: 800px;\n  }\n}\n\n@media (min-width: 1920px) {\n  .VPDoc:not(.has-aside) .content-container {\n    max-width: 1024px;\n  }\n\n  .VPDoc.has-aside .content-container {\n    max-width: 848px;\n  }\n}\n\n\n/* Ultra-wide display support */\n@media (min-width: 2000px) {\n  :root {\n    --vp-sidebar-width: 380px;\n  }\n\n  .VPDoc:not(.has-aside) .content-container {\n    max-width: 1100px;\n  }\n\n  .VPDoc.has-aside .content-container {\n    max-width: 920px;\n  }\n\n  /* Increase aside width for better balance */\n  .VPDoc .aside {\n    max-width: 280px;\n  }\n}\n\n@media (min-width: 2400px) {\n  :root {\n    --vp-sidebar-width: 400px;\n  }\n\n  .VPDoc:not(.has-aside) .content-container {\n    max-width: 1200px;\n  }\n\n  .VPDoc.has-aside .content-container {\n    max-width: 1000px;\n  }\n\n  .VPDoc .aside {\n    max-width: 300px;\n  }\n}\n\n/**\n * Component: Sidebar\n * -------------------------------------------------------------------------- */\n\n.VPSidebarItem.level-0 > .item > .text {\n  font-weight: 600;\n  color: var(--c-navy-800);\n}\n\nhtml.dark .VPSidebarItem.level-0 > .item > .text {\n  color: var(--c-mint-300);\n}\n\n/* Active item styling */\n.VPSidebarItem.is-active > .item > .indicator {\n  background-color: var(--c-mint-500);\n}\n\n.VPSidebarItem.is-active > .item > .text {\n  color: var(--c-mint-600);\n}\n\nhtml.dark .VPSidebarItem.is-active > .item > .text {\n  color: var(--c-mint-400);\n}\n\n/* Hover effect */\n.VPSidebarItem .item:hover .text {\n  color: var(--c-mint-600);\n}\n\nhtml.dark .VPSidebarItem .item:hover .text {\n  color: var(--c-mint-400);\n}\n\n/**\n * Component: Algolia / DocSearch\n * -------------------------------------------------------------------------- */\n\n.DocSearch {\n  --docsearch-primary-color: var(--vp-c-brand-1) !important;\n}\n\nhtml.dark .DocSearch,\nhtml.dark .DocSearch-Modal {\n  --docsearch-container-background: rgba(9, 10, 17, 0.8);\n  --docsearch-modal-background: var(--vp-c-black);\n  --docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;\n  --docsearch-hit-color: #e2ebf0;\n  --docsearch-hit-active-color: var(--c-navy-900);\n  --docsearch-hit-shadow: none;\n  --docsearch-hit-background: var(--vp-c-black-light);\n  --docsearch-footer-background: var(--vp-c-black-light);\n  --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5),\n    0 -4px 8px 0 rgba(0, 0, 0, 0.2);\n  --docsearch-logo-color: var(--vp-c-brand-1);\n  --docsearch-muted-color: var(--c-navy-400);\n}\n\nhtml.dark .DocSearch-Logo svg .cls-1,\nhtml.dark .DocSearch-Logo svg .cls-2 {\n  fill: var(--vp-c-brand-1);\n}\n\n/**\n * Component: Links\n * -------------------------------------------------------------------------- */\n\n.vp-doc a {\n  color: var(--c-mint-600);\n  text-decoration: underline;\n  text-decoration-color: var(--c-mint-200);\n  text-underline-offset: 2px;\n  transition: all 0.2s ease;\n}\n\n.vp-doc a:hover {\n  color: var(--c-mint-700);\n  text-decoration-color: var(--c-mint-400);\n}\n\nhtml.dark .vp-doc a {\n  color: var(--c-mint-400);\n  text-decoration-color: var(--c-mint-700);\n}\n\nhtml.dark .vp-doc a:hover {\n  color: var(--c-mint-300);\n  text-decoration-color: var(--c-mint-500);\n}\n\n/**\n * Component: Book Figures\n * -------------------------------------------------------------------------- */\n\n.vp-doc img[src^='/figures/'] {\n  display: block;\n  max-width: 100%;\n  height: auto;\n  margin: 1.5rem auto;\n  border: 1px solid rgba(154, 170, 196, 0.35);\n  border-radius: 8px;\n  background: #0f1724;\n  box-shadow: 0 12px 28px rgba(26, 39, 68, 0.12);\n}\n\nhtml.dark .vp-doc img[src^='/figures/'] {\n  border-color: rgba(139, 228, 211, 0.24);\n  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.32);\n}\n\n.vp-doc img[src^='/figures/_brand/'],\n.vp-doc img.author-avatar,\n.vp-doc img.sponsors-image {\n  border: 0;\n  background: transparent;\n  box-shadow: none;\n}\n\n.vp-doc img.author-avatar {\n  width: 160px;\n  height: 160px;\n  object-fit: cover;\n  margin: 1rem 0 1.25rem;\n  border: 1px solid rgba(154, 170, 196, 0.35);\n  border-radius: 8px;\n  box-shadow: 0 12px 28px rgba(26, 39, 68, 0.12);\n}\n\nhtml.dark .vp-doc img.author-avatar {\n  border-color: rgba(139, 228, 211, 0.24);\n  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.32);\n}\n\n.vp-doc .sponsors-block {\n  text-align: center;\n}\n\n.vp-doc a.sponsors-image-link {\n  display: block;\n}\n\n.vp-doc img.sponsors-image {\n  width: min(100%, 560px);\n  margin: 1.5rem auto;\n}\n\n.vp-doc img.sponsors-image--dark {\n  display: none;\n}\n\nhtml.dark .vp-doc img.sponsors-image--light {\n  display: none;\n}\n\nhtml.dark .vp-doc img.sponsors-image--dark {\n  display: block;\n}\n\n/**\n * Component: Sponsor Button\n * -------------------------------------------------------------------------- */\n\n.become-sponsor {\n  font-size: 0.9em;\n  font-weight: 700;\n  width: auto;\n  text-align: center;\n  background-color: transparent;\n  padding: 0.75em 2em;\n  border-radius: 2em;\n  transition: all 0.15s ease;\n  box-sizing: border-box;\n  border: 2px solid var(--c-mint-600);\n}\n\n.become-sponsor:hover {\n  background-color: var(--c-mint-500);\n  text-decoration: none;\n  border-color: var(--c-mint-500);\n  color: #ffffff;\n}\n\n.sponsors-top .become-sponsor {\n  font-size: 0.75em;\n  padding: 0.2em;\n  width: auto;\n  max-width: 150px;\n}\n\n/**\n * Component: Kbd\n * -------------------------------------------------------------------------- */\n\nkbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font-size: 0.65em;\n  color: var(--vp-c-text-1);\n  vertical-align: middle;\n  background-color: var(--vp-c-bg-mute);\n  border: solid 1px var(--vp-c-bg-soft-mute);\n  border-radius: 6px;\n  box-shadow: inset 0 -1px 0 var(--vp-c-bg-soft-mute);\n  line-height: 0.95em;\n}\n\n/**\n * Custom Scrollbar\n * -------------------------------------------------------------------------- */\n\n::-webkit-scrollbar {\n  width: 8px;\n  height: 8px;\n}\n\n::-webkit-scrollbar-track {\n  background: var(--vp-c-bg-soft);\n}\n\n::-webkit-scrollbar-thumb {\n  background: var(--c-mint-300);\n  border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: var(--c-mint-400);\n}\n\nhtml.dark ::-webkit-scrollbar-thumb {\n  background: var(--c-mint-700);\n}\n\nhtml.dark ::-webkit-scrollbar-thumb:hover {\n  background: var(--c-mint-600);\n}\n\n/**\n * Reduced Motion Support\n * -------------------------------------------------------------------------- */\n\n@media (prefers-reduced-motion: reduce) {\n  *,\n  *::before,\n  *::after {\n    animation-duration: 0.01ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0.01ms !important;\n  }\n}\n"
  },
  {
    "path": "book/online-book/src/00-introduction/010-about.md",
    "content": "# Introduction\n\n## Purpose of this book\n\nThank you for picking up this book!  \nI am truly delighted if you have even a slight interest in this book.  \nLet me first summarize the purpose of this book.\n\n**☆ Purpose**\n\n- **Deepen understanding of Vue.js**  \n  What is Vue.js? How is it structured?\n- **Be able to implement basic functions of Vue.js**  \n  Actually try implementing basic functionalities.\n- **Read the source code of vuejs/core**  \n  Understand the relation between implementations and the official code, and grasp how they are really built.\n\nI've provided a rough outline of goals, but it's not necessary to fulfill all of them, nor is it a call to pursue perfection.  \nWhether you read it cover-to-cover, or just pick out the parts that interest you, it's up to you.  \nI'd be happy if you find even a small part of this book useful!\n\n## Intended Audience\n\n- **Those who have experience with Vue.js**\n- **Can write in TypeScript**\n\nWith just these two prerequisites, no other knowledge is needed.  \nWhile you might encounter unfamiliar terms throughout the book, I've tried my best to exclude any prior knowledge and explain things along the way, aiming to make this book self-contained.  \nHowever, if you come across terms that shouldn't be used for Vue.js or TypeScript, I recommend learning from the respective resources first.  \n(Basic functionalities are enough! (There's no need to delve deep))\n\n## What this book (and the author) is conscious of (and wants to achieve)\n\nBefore diving in, I'd like to share a few things that I've been especially conscious of while writing this book.  \nI hope you keep these in mind as you read, and if there are any areas where I've missed the mark, please let me know.\n\n- **Eliminating the need for prior knowledge**  \n  While this might overlap with the \"Intended Audience\" section mentioned earlier, I strive to make this book as self-contained as possible,  \n  minimizing the need for prior knowledge and providing explanations as needed.  \n  This is because I want to make the explanations as clear as possible to as many readers as I can.  \n  Those with a good deal of experience may find some of the explanations a bit verbose, but I ask for your understanding.\n\n- **Incremental implementation**  \n  One of the book's goals is to incrementally implement Vue.js by hand. This means that the book focuses on a hands-on approach,  \n  and when it comes to implementation, I emphasize building in small, incremental steps.  \n  To be more specific, it's about \"minimizing non-working states.\"  \n  Instead of having something that won't work until it's complete, the aim is to keep it functioning at every stage.  \n  This reflects my personal approach to coding – writing non-functional code continuously can be disheartening.  \n  Even if it's imperfect, always having something in motion makes the process more enjoyable.  \n  It's about experiencing little victories like, \"Yes! It works up to this point now!\"\n\n- **Avoiding biases towards specific frameworks, libraries, or languages**  \n  While this book focuses on Vue.js, there are countless excellent frameworks, libraries, and languages out there today.  \n  In fact, I have my favorites beyond Vue.js, and I frequently benefit from insights and services built with them.  \n  The purpose of this book is purely to \"understand Vue.js\" and doesn't venture into ranking or judging other tools.\n\n## Topics and structure of this online book\n\nSince this book has turned out quite voluminous, I've set achievement milestones and divided it into different sections.\n\n- **Minimal Example Section**  \n   Here, Vue.js is implemented in its most basic form.  \n   Although this section covers the smallest set of features, it will deal with  \n   the Virtual DOM, the Reactivity System, the Compiler, and SFC (Single File Components) support.  \n   However, these implementations are far from practical and are highly simplified.  \n   But, for those wanting a broad overview of Vue.js, this section offers sufficient insight.  \n   Being an introductory section, the explanations here are more detailed than in other parts.  \n   By the end of this section, readers should be somewhat comfortable reading the official Vue.js source code. Functionally, you can expect the code to do roughly the following...\n\n  ```vue\n  <script>\n  import { reactive } from 'chibivue'\n\n  export default {\n    setup() {\n      const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n      const changeMessage = () => {\n        state.message += '!'\n      }\n\n      const handleInput = e => {\n        state.input = e.target?.value ?? ''\n      }\n\n      return { state, changeMessage, handleInput }\n    },\n  }\n  </script>\n\n  <template>\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\">click me!</button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n    </div>\n  </template>\n\n  <style>\n  .container {\n    height: 100vh;\n    padding: 16px;\n    background-color: #becdbe;\n    color: #2c3e50;\n  }\n  </style>\n  ```\n\n  ```ts\n  import { createApp } from 'chibivue'\n  import App from './App.vue'\n\n  const app = createApp(App)\n\n  app.mount('#app')\n  ```\n\n- **Basic Virtual DOM Section**  \n  In this section, we will implement a fairly practical patch rendering functionality for the Virtual DOM. While we won't be implementing features like [Suspense](https://vuejs.org/guide/built-ins/suspense) or other optimizations, it will be proficient enough to handle basic rendering tasks. We will also implement the scheduler here.\n\n- **Basic Reactivity System Section**  \n  Although we implemented the reactive API in the Minimal Example section, in this section we will implement other APIs. Starting from basic APIs like ref, watch, and computed, we'll also delve into more advanced APIs like effectScope and the shallow series.\n\n- **Basic Component System Section**  \n  Here, we will undertake the basic implementations related to the Component System. In fact, since we'll have already set the base for the Component System in the Basic Virtual DOM section, here we'll focus on other aspects of the Component System. This includes features like props/emit, provide/inject, extensions to the Reactivity System, and lifecycle hooks.\n\n- **Basic Template Compiler Section**  \n  In addition to a compiler for the Virtual DOM system implemented in the Basic Virtual DOM section, we will implement directives like v-on, v-bind, and v-for. Generally, this will involve the component's template option, and we won't cover SFC (Single File Components) here.\n\n- **Basic SFC Compiler Section**  \n  Here, we will implement a somewhat practical SFC compiler while utilizing the template compiler implemented in the Basic Template Compiler section.  \n  Specifically, we will implement script setup and compiler macros.  \n  At this point, the experience will be quite close to using regular Vue.\n\n- **Web Application Essentials Section**  \n  By the time we finish the Basic SFC Compiler section, we'll have a somewhat practical set of Vue.js features. However, to develop a web application, there's still a lot missing. For instance, we'll need tools to manage global state and routers. In this section, we'll develop such external plugins, aiming to make our toolkit even more practical from a \"web application development\" perspective.\n\n## About opinions and questions on this book\n\nI intend to respond to questions and feedback about this book to the best of my ability. Feel free to reach out on Twitter (either through DMs or directly on the timeline). Since I've made the repository public, you can also post issues there. I'm aware that my own understanding isn't perfect, so I appreciate any feedback. If you find any explanations unclear or challenging, please don't hesitate to ask. My goal is to spread clear and correct explanations to as many people as possible, and I hope we can build this together.\n\nhttps://x.com/ubugeeei\n\n## About the Discord Server\n\nWe have created a Discord Server for this book! (2024/01/01)  \n~~Here, we share announcements, provide support for questions and tips related to this online book.~~ \\\nWe also welcome casual conversations, so let's have fun communicating with other chibivue users.  \nCurrently, most of the conversations are in Japanese as there are many Japanese speakers, but non-Japanese speakers are also welcome to join without hesitation! (It's completely fine to use your native language)\n\nRecently, We’ve been actively contributing not only to chibivue but also as part of the Japanese community server for Vue.js!\n\n### What we do roughly\n\n- Self-introduction (optional)\n- Announcements related to chibivue (such as updates)\n- Sharing tips\n- Answering questions\n- Responding to requests\n- Casual conversations\n\n### How to join\n\nHere is the invitation link: https://discord.gg/aVHvmbmSRy\n\nYou can also join from the Discord button on the top right of this book's header.\n\n## About the Author\n\n**ubugeeei (mononokeking)**\n\n<img class=\"author-avatar\" src=\"/figures/_people/ubugeeei-avatar.jpg\" alt=\"ubugeeei\" width=\"160\" height=\"160\">\n\nMember of the [Vue.js](https://vuejs.org/about/team.html) Core Team, core staff of the [Vue.js Japan User Group](https://github.com/vuejs-jp), [Vite+](https://github.com/voidzero-dev/vite-plus) Core Contributor, and Chief Engineer at [Mates](https://github.com/mates-inc).\\\n[chibivue land](https://github.com/chibivue-land) King. https://chibivue.land\n\nI build tools and books around Vue, language processors, and developer experience, including [chibivue](https://github.com/chibivue-land/chibivue), [Vize](https://github.com/ubugeeei/vize), [Ox Content](https://github.com/ubugeeei/ox-content), [reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor), and [Vapor Moon](https://github.com/ubugeeei/vapor-moon).\n\nhttps://wtrclred.io/\n\nIf you'd like, I would appreciate your support as a sponsor! https://github.com/sponsors/ubugeeei\n\n## Sponsors\n\n<div class=\"sponsors-block\">\n<a class=\"sponsors-image-link\" href=\"https://github.com/sponsors/ubugeeei\">\n  <img class=\"sponsors-image sponsors-image--light\" src=\"/figures/_sponsors/ubugeeei-sponsors.png\" alt=\"ubugeeei's sponsors\" />\n  <img class=\"sponsors-image sponsors-image--dark\" src=\"/figures/_sponsors/ubugeeei-sponsors-dark.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n<p>If you'd like to support my work, I would greatly appreciate it!</p>\n<p><a href=\"https://github.com/sponsors/ubugeeei\">https://github.com/sponsors/ubugeeei</a></p>\n\n</div>\n"
  },
  {
    "path": "book/online-book/src/00-introduction/020-what-is-vue.md",
    "content": "# What is Vue.js?\n\n## A quick recap about Vue.js\n\nLet's get straight to the point.  \nBut before that, let's quickly recap what Vue.js is all about.\n\n## What's Vue.js again?\n\nVue.js is a \"friendly, high-performance, and versatile framework for building web user interfaces.\"  \nThis is stated on the official documentation's homepage.  \nFor this, I believe it's clearer to directly quote the official words without adding my own interpretation, so I've cited them below:\n\n> Vue (pronounced /vjuː/, like view) is a JavaScript framework for building user interfaces. It builds on top of standard HTML, CSS, and JavaScript and provides a declarative and component-based programming model that helps you efficiently develop user interfaces, be they simple or complex.\n\n> Declarative Rendering: Vue extends standard HTML with a template syntax that allows us to declaratively describe HTML output based on JavaScript state.\n\n> Reactivity: Vue automatically tracks JavaScript state changes and efficiently updates the DOM when changes happen.\n\n> Here's a minimal example:\n>\n> ```ts\n> import { createApp } from 'vue'\n>\n> createApp({\n>   data() {\n>     return {\n>       count: 0,\n>     }\n>   },\n> }).mount('#app')\n> ```\n>\n> ```html\n> <div id=\"app\">\n>   <button @click=\"count++\">Count is: {{ count }}</button>\n> </div>\n> ```\n\n[reference source](https://vuejs.org/guide/introduction.html#what-is-vue)\n\nFor declarative rendering and reactivity, we will delve into them in detail in their respective chapters, so a high-level understanding is fine for now.\n\n![Vue.js as an implementation map](/figures/00-introduction/what-is-vue/vue-implementation-map.svg)\n\nAlso, the term \"framework\" has come up here, and Vue.js promotes itself as a \"progressive framework.\" For more on that, I believe it's best to refer directly to the following section of the documentation:\n\nhttps://vuejs.org/guide/introduction.html#the-progressive-framework\n\n## The difference between the official documentation and this book\n\nThe official documentation focuses on \"how to use Vue.js,\" with an abundance of tutorials and guides provided.\n\nHowever, this book takes a slightly different approach, focusing on \"how Vue.js is implemented.\" We'll write actual code to create a mini version of Vue.js.\n\nAlso, this book isn't an official publication and may not be exhaustive. There might be some errors or omissions, so I'd appreciate any feedback or corrections.\n"
  },
  {
    "path": "book/online-book/src/00-introduction/030-vue-core-components.md",
    "content": "# Key Elements that Constitute Vue.js\n\n## Vue.js Repository\n\nVue.js can be found in this repository:  \nhttps://github.com/vuejs/core\n\nInterestingly, this is the repository for v3. For v2 and earlier versions, you can find it in another repository:  \nhttps://github.com/vuejs/vue\n\nFor the sake of this discussion, we will be focusing on the core repository (v3).\n\n## Main Elements that Make Up Vue.js\n\nLet's first get a holistic understanding of Vue.js's implementation. \\\nThere is a markdown file about contributions in the Vue.js repository;  \nif you're interested, you can check it out to get insights about its architecture. (Though, it's fine if you skip it.)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md\n\nIn broad strokes, Vue.js comprises the following major components:\n\n## Runtime\n\nThe runtime encompasses everything that affects the actual operation - from rendering to component state management. \\\nThis refers to the entirety of a Vue.js-developed web application that operates on a browser or a server (in the case of SSR). Specifically, it includes:\n\n### Component System\n\nVue.js is a component-oriented framework. Depending on the user's requirements, you can maintainably create and encapsulate components for reuse.\\\nIt also offers functionalities like state sharing between components (props/emits or provide/inject) and lifecycle hooks.\n\n### Reactivity System\n\nIt tracks the state held by components and updates the screen when changes occur. \\\nThis monitoring and responding mechanism is called reactivity.\n\n```ts\nimport { ref } from 'vue'\n\nconst count = ref(0)\n\n// When this function is executed, the screen displaying the count will also update\nconst increment = () => {\n  count.value++\n}\n```\n\n(It's fascinating how the screen updates just by changing a value, right?)\n\n### Virtual DOM System\n\nThe Virtual DOM system is another one of Vue.js's potent mechanisms. It defines a JavaScript object imitating the DOM on JS's runtime. \\\nWhen updating, it compares the current Virtual DOM to a new one and reflects only the differences to the real DOM. \\\nWe'll delve deeper into this in a dedicated chapter.\n\n## Compiler\n\nThe compiler is responsible for converting the developer interface to the internal implementation.\\\nBy \"developer interface,\" we mean the boundary between \"developers using Vue.js for web application development\" and \"Vue's internal operations.\"\\\nEssentially, when you write using Vue.js, there are parts that are clearly not pure JavaScript – like template directives or Single File Components. \\\nVue.js provides these syntaxes and converts them to pure JavaScript.\\\nThis feature is only used during the development stage and isn't part of the actual operational web application.\n\\(it merely compiles to JavaScript code).\n\nThe compiler has two main sections:\n\n### Template Compiler\n\nAs the name suggests, this is the compiler for the template part. \\\nSpecifically, it handles directives like v-if or v-on, user component notations (like <Counter />), and functionalities like slots.\n\n### SFC Compiler\n\nAs you might guess, this stands for Single File Component compiler.\\\nIt allows you to define a component's template, script, and style within a single .vue file.\\\nFunctions used in script setup like [defineProps or defineEmits](https://vuejs.org/api/sfc-script-setup#defineprops-defineemits) are also provided by this compiler. \\\nAnd this SFC compiler is often used in conjunction with tools like Webpack or Vite.\\\nThe implementation as a plugin for other tools doesn't reside in the core repository.\\\nThe main functionality of the SFC compiler is in the core, but the plugins are implemented in different repositories.\\\n(Reference: [vitejs/vite-plugin-vue](https://github.com/vitejs/vite-plugin-vue))\n\nBy the way, we will be implementing an actual Vite plugin to operate our custom SFC compiler.\n\n## Peeking into the vuejs/core Directory\n\nNow that we have a rough understanding of Vue's major elements, let's see how the actual source code looks (although we're just discussing directories). \\\nThe primary source code is stored in the \"packages\" directory.\n\nhttps://github.com/vuejs/core/tree/main/packages\n\nSome of the key directories to focus on are:\n\n- compiler-core\n- compiler-dom\n- compiler-sfc\n- reactivity\n- runtime-core\n- runtime-dom\n- vue\n\nFor understanding their interdependencies, the diagram in the contribution guide is particularly insightful.\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n<br/>\nIn this book, we will provide implementation and explanation for all of these topics.\n"
  },
  {
    "path": "book/online-book/src/00-introduction/040-setup-project.md",
    "content": "# How to Proceed with This Book and Environment Setup\n\n## Web Playground\n\nThis book provides a **Web Playground** where you can try the implementation code for each chapter directly in your browser.\nYou can edit and run code immediately without any environment setup, so try experiencing chibivue in action here first!\n\n### How to Start the Playground\n\n```sh\n$ git clone https://github.com/chibivue-land/chibivue\n$ cd chibivue\n$ pnpm install\n$ pnpm play\n```\n\nAccess the URL displayed in your browser (e.g., `http://localhost:5173/`) to launch the Playground.\n\n### Playground Layout\n\n![Initial Web Playground screen](/figures/00-introduction/setup-project/web-playground-initial.png)\n\nThe Playground consists of four areas:\n\n| Area | Description |\n|------|-------------|\n| **Explorer (left)** | Displays the project file tree. Click a file to open it in the editor |\n| **Editor (center)** | Edit code with Monaco Editor |\n| **Preview (right)** | Shows a preview of the dev server running on WebContainer |\n| **Terminal / Console (bottom)** | View terminal output and console.log contents |\n\n### How to Use\n\n1. **Select a chapter**\n   Select the chapter you want to study from the dropdown at the top of the screen.\n   You can also filter chapter names using the search box.\n\n2. **Click Run**\n   Click the \"Run\" button to start WebContainer, install dependencies, and start the dev server.\n   It takes a little time the first run, but after a while, the results will be displayed in the Preview area.\n\n3. **Edit the code**\n   Edit the code in the editor and click the \"Apply\" button to apply your changes.\n   Changes are reflected in real-time via HMR (Hot Module Replacement).\n\n4. **Check the console**\n   Click the \"Console\" tab to view output from console.log and other sources.\n\n![Web Playground console output](/figures/00-introduction/setup-project/web-playground-console.png)\n\n::: tip\nThe Web Playground uses [WebContainer](https://webcontainers.io/).\nIt may not work in some browsers or environments. In that case, please refer to the local environment setup below.\n:::\n\n## How to Proceed with This Book\n\nWe will promptly start with a simple implementation of Vue.js. Here are some points to keep in mind, precautions, and other essential information:\n\n- The project name will be \"chibivue.\" We will refer to the basic Vue.js implementations covered in this book as \"chibivue.\"\n- As initially mentioned, our primary approach will be \"repeating small developments.\"\n- Source codes for each phase are included in the appendix of this book and can be found at https://github.com/chibivue-land/chibivue/tree/main/book/impls. We will not provide detailed explanations for all the source code in the book, so please refer to the appendix as needed.\n- The final code depends on several packages. A common issue with DIY content is the debate over \"how much one should implement by hand to call it homemade.\" While we won't write all source code by hand in this book, we will actively use packages similar to those used in Vue.js's official code. For example, we'll use [Babel](https://babeljs.io/). Rest assured, we aim to make this book as beginner-friendly as possible, providing minimal explanations for necessary packages.\n\n## Environment Setup\n\nNow, let's quickly move on to setting up the environment! \\\nI'll list the tools and versions we'll be using:\n\n- Runtime: [Node.js](https://nodejs.org/en) v24\n- Language: [TypeScript](https://www.typescriptlang.org/)\n- Package Manager: [pnpm](https://pnpm.io/) v10\n- Build Tool: [Vite](https://vite.dev/) v8\n\n## Installing Node.js\n\nMost of you are probably familiar with this step. Please set it up on your own. We will skip the detailed explanation here.\n\n## Installing pnpm\n\nMany of you might typically use npm or yarn. For this book, we will be using pnpm, so please install it as well. The commands are mostly similar to npm.\nhttps://pnpm.io/installation\n\n\n## Creating the Project\n\n::: details Quick Start for those in a hurry ...\n\nAlthough I'll be explaining the steps to create a project manually, there's actually a tool prepared for the setup.  \nIf you find the manual process tedious, please feel free to use this tool!\n\n1. Clone chibivue.\n\n   ```sh\n   $ git clone https://github.com/chibivue-land/chibivue\n   ```\n\n2. Execute the script.  \n    Enter the path of the directory you want to set up.\n\n   ```sh\n   $ cd chibivue\n   $ pnpm setup:book ../my-chibivue-project\n   ```\n\n:::\n\nCreate the project in any directory of your choice. For convenience, we'll denote the project's root path as `~` (e.g., `~/src/main.ts`).\n\nThis time, we will separate the main \"chibivue\" from a playground to test its functionality. The playground will simply invoke \"chibivue\" and bundle it with Vite. We anticipate a structure like this.\n\n```\n~\n|- examples\n|    |- playground\n|\n|- packages\n|- tsconfig.js\n```\n\nWe will implement the playground in a directory named \"examples.\"\nWe will implement the core TypeScript files for chibivue in \"packages\" and import them from the example side.\n\nBelow are the steps to construct it.\n\n### Building the Main Project\n\n```sh\n## Please create a directory specifically for chibivue and navigate into it. (Such notes will be omitted hereafter.)\npwd # ~/\npnpm init\npnpm add -D @types/node\nmkdir packages\ntouch packages/index.ts\ntouch tsconfig.json\n```\n\nContents of tsconfig.json\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\nContents of packages/index.ts\n\n```ts\nconsole.log(\"Hello, World\")\n```\n\n### ### Building the Playground Side\n\n```sh\npwd # ~/\nmkdir examples\ncd examples\npnpm dlx create-vite\n\n## --------- Setting up with the Vite CLI\n## Project name: playground\n## Select a framework: Vanilla\n## Select a variant: TypeScript\n```\n\nRemove unnecessary items from the project created with Vite.\n\n```sh\npwd # ~/examples/playground\nrm -rf public\nrm -rf src # We will recreate it since there are unnecessary files.\nmkdir src\ntouch src/main.ts\n```\n\nContents of src/main.ts\n\n※ For now, there will be an error after \"from,\" but we will address this in the upcoming steps, so it's not a problem.\n\n```ts\nimport \"chibivue\"\n```\n\nModify index.html as follows.\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\nConfigure an alias in the Vite project to be able to import what you implemented in chibivue.\n\n```sh\npwd # ~/examples/playground\ntouch vite.config.js\n```\n\nContents of vite.config.js\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n})\n```\n\nModify tsconfig.json as follows.\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Node\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n```\n\nLastly, let's add a command to the package.json of the chibivue project to launch the playground and try starting it!\n\nAppend the following to ~/package.json\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  }\n}\n```\n\n```sh\npwd # ~\npnpm dev\n```\n\nAccess the developer server that started with this command. If a message displays, then the setup is complete.\n\n![Hello chibivue rendered in the browser](/figures/00-introduction/setup-project/hello-chibivue-result.png)\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls//00_introduction/010_project_setup)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/010-create-app-api.md",
    "content": "# First Rendering and createApp API\n\n## Where to start?\n\nNow, let's start implementing chibivue step by step. How should we proceed with the implementation?\n\n<KawaikoNote variant=\"question\" title=\"Let's start implementing!\">\n\nThis is the exciting moment to dive into Vue.js internals!\n\"Where to start\" is actually an important point.\n\n</KawaikoNote>\n\nThis is something the author always keeps in mind when creating something new: first, think about how the software will be used. \\\nFor convenience, let's call this \"Developer Interface\".\n\nHere, \"developer\" refers to the person who develops web applications using chibivue, not the developer of chibivue itself.\\\nIn other words, let's refer to the developer interface of the original Vue.js as a reference when developing chibivue. \\\nSpecifically, let's take a look at what to write when developing web applications with Vue.js.\n\n## Developer Interface Levels?\n\nWhat we need to be careful about here is that Vue.js has multiple developer interfaces, each with a different level. Here, the level refers to how close it is to raw JavaScript. \\\nFor example, the following are examples of developer interfaces for displaying HTML with Vue:\n\n1. Write the template in Single File Component\n\n```vue\n<!-- App.vue -->\n<template>\n  <div>Hello world.</div>\n</template>\n```\n\n```ts\nimport { createApp } from 'vue'\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\n2. Use the template option\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  template: '<div>Hello world.</div>',\n})\n\napp.mount('#app')\n```\n\n3. Use the render option and h function\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\nThere are other options as well, but let's consider these three developer interfaces. \\\nWhich one is closest to raw JavaScript? The answer is \"using the render option and h function\" (option 3). \\\nOption 1 requires the implementation of the SFC compiler and bundler (or loader), and option 2 requires compiling the HTML passed to the template (converting it to JavaScript code) in order to work.\n\nFor convenience, let's call the developer interface that is closer to raw JS \"low-level developer interface\".\\\nAnd the important thing here is to \"start implementing from the low-level part\". \\\nThe reason for this is that in many cases, high-level descriptions are converted to low-level descriptions and executed. \\\nIn other words, both option 1 and 2 are ultimately converted internally to the form of option 3. \\\nThe implementation of this conversion is called a \"compiler\".\n\n<KawaikoNote variant=\"funny\" title=\"Start from low-level!\">\n\n\"Low-level\" might sound weak, but it's actually the opposite!\nIt's the foundation, and without a solid foundation, you can't build higher-level features.\n\n</KawaikoNote>\n\nSo, let's start by implementing a developer interface like option 3!\n\n## createApp API and Rendering\n\nAlthough we aim for the form of option 3, we still don't understand the h function well, and since this book aims for incremental development, let's not aim for the form of option 3 right away. \\\nInstead, let's start by implementing a simple rendering function that returns a message to be displayed.\n\nImage ↓\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n## Implementing it right away\n\nLet's create the createApp function in `~/packages/index.ts`. \\\nNote: Since the output of \"Hello, World\" is not necessary, we will remove it.\n\n```ts\nexport type Options = {\n  render: () => string\n}\n\nexport type App = {\n  mount: (selector: string) => void\n}\n\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render()\n      }\n    },\n  }\n}\n```\n\nIt's very simple. Let's try it in the playground.\n\n`~/examples/playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\nWe were able to display the message on the screen! Well done!\n\n![createApp example rendered in the browser](/figures/10-minimum-example/create-app-api/hello-create-app-result.png)\n\n<KawaikoNote variant=\"surprise\" title=\"First step complete!\">\n\nWith just a few dozen lines of code, a Vue.js-style app is running!\nThis small step is a big step toward understanding frameworks.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/010_create_app)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/015-package-architecture.md",
    "content": "# Package Architecture\n\n## Refactoring\n\nYou might think, \"Huh? We've only implemented this much and you want to refactor?\" But one of the goals of this book is to \"be able to read the Vue.js source code.\"\n\nWith that in mind, I want to always be conscious of the file and directory structure in the style of Vue.js. So, please allow me to do a little refactoring...\n\n### Vue.js Design\n\n#### runtime-core and runtime-dom\n\nLet me explain a little about the structure of the official Vue.js. In this refactoring, we will create two directories: \"runtime-core\" and \"runtime-dom\".\n\n<KawaikoNote variant=\"question\" title=\"Why split them?\">\n\n\"Why split when the code already works?\" you might ask.\\\nVue.js is designed to run not only in browsers, but also in SSR (Server-Side Rendering) and native apps (Vue Native).\\\nThat's why \"pure logic\" and \"DOM operations\" are separated!\n\n</KawaikoNote>\n\nTo explain what each of them is, \"runtime-core\" contains the core functionality of Vue.js runtime. It may be difficult to understand what is core and what is not at this stage.\n\nSo, I think it would be easier to understand by looking at the relationship with \"runtime-dom\". As the name suggests, \"runtime-dom\" is a directory that contains DOM-dependent implementations. Roughly speaking, it can be understood as \"browser-dependent operations\". It includes DOM operations such as querySelector and createElement.\n\nIn runtime-core, we don't write such operations, but instead, we design it to describe the core logic of Vue.js runtime in the world of pure TypeScript. For example, it includes implementations related to Virtual DOM and Components. Well, I think it will become clearer as the development of chibivue progresses, so if you don't understand, please refactor as described in the book for now.\n\n#### Roles and Dependencies of Each File\n\nWe will now create some files in runtime-core and runtime-dom. The necessary files are as follows:\n\n```sh\npwd # ~\nmkdir packages/runtime-core\nmkdir packages/runtime-dom\n\n## core\ntouch packages/runtime-core/index.ts\ntouch packages/runtime-core/apiCreateApp.ts\ntouch packages/runtime-core/component.ts\ntouch packages/runtime-core/componentOptions.ts\ntouch packages/runtime-core/renderer.ts\n\n## dom\ntouch packages/runtime-dom/index.ts\ntouch packages/runtime-dom/nodeOps.ts\n```\n\nAs for the roles of these files, it may be difficult to understand just by explaining in words, so please refer to the following diagram:\n\n![runtime-core and runtime-dom responsibilities](/figures/10-minimum-example/package-architecture/runtime-core-dom-overview.svg)\n\n#### Design of the Renderer\n\nAs mentioned earlier, Vue.js separates the parts that depend on the DOM from the pure core functionality of Vue.js. First, I want you to pay attention to the renderer factory in \"runtime-core\" and the nodeOps in \"runtime-dom\". In the example we implemented earlier, we directly rendered in the mount method of the app returned by createApp.\n\n```ts\n// This is the code from earlier\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render() // Rendering\n      }\n    },\n  }\n}\n```\n\nAt this point, the code is short and not complex at all, so it seems fine at first glance. However, it will become much more complex as we write the patch rendering logic for the Virtual DOM in the future. In Vue.js, this part responsible for rendering is separated as \"renderer\". That is \"runtime-core/renderer.ts\". When it comes to rendering, it is easy to imagine that it depends on the API (document) that controls the DOM in the browser in an SPA (creating elements, setting text, etc.). Therefore, in order to separate this part that depends on the DOM from the core rendering logic of Vue.js, some tricks have been made. Here's how it works:\n\n- Implement an object in `runtime-dom/nodeOps` for DOM operations.\n- Implement a factory function in `runtime-core/renderer` that generates an object that only contains the logic for rendering. In doing so, make sure to pass the object that handles nodes (not limited to DOM) as an argument to the factory function.\n- Use the factories for `nodeOps` and `renderer` in `runtime-dom/index.ts` to complete the renderer.\n\nThis is the part highlighted in red in the diagram.\n![Renderer dependency injection](/figures/10-minimum-example/package-architecture/renderer-dependency-injection.svg)\n\nLet me explain the source code. At this point, the rendering feature of the Virtual DOM has not been implemented yet, so we will create it with the same functionality as before.\n\nFirst, implement the interface for the object used for node (not limited to DOM) operations in `runtime-core/renderer`.\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  setElementText(node: HostNode, text: string): void\n}\n\nexport interface RendererNode {\n  [key: string]: any\n}\n\nexport interface RendererElement extends RendererNode {}\n```\n\nCurrently, there is only the `setElementText` function, but you can imagine that functions like `createElement` and `removeChild` will be implemented in the future.\n\nRegarding `RendererNode` and `RendererElement`, please ignore them for now. (The implementation here is just defining a generic type for objects that become nodes, without depending on the DOM.)  \nImplement the renderer factory function in this file, which takes `RendererOptions` as an argument.\n\n```ts\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  message: string,\n  container: HostElement,\n) => void\n\nexport function createRenderer(options: RendererOptions) {\n  const { setElementText: hostSetElementText } = options\n\n  const render: RootRenderFunction = (message, container) => {\n    hostSetElementText(container, message) // In this case, we are simply inserting the message, so the implementation is like this\n  }\n\n  return { render }\n}\n```\n\nNext, implement the `nodeOps` in `runtime-dom/nodeOps`.\n\n```ts\nimport { RendererOptions } from '../runtime-core'\n\nexport const nodeOps: RendererOptions<Node> = {\n  setElementText(node, text) {\n    node.textContent = text\n  },\n}\n```\n\nThere is nothing particularly difficult here.\n\nNow, let's complete the renderer in `runtime-dom/index.ts`.\n\n```ts\nimport { createRenderer } from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\n```\n\nWith this, the refactoring of the renderer is complete.\n\n#### DI and DIP\n\nLet's take a look at the design of the renderer. To summarize:\n\n- Implement a factory function in `runtime-core/renderer` to generate the renderer.\n- Implement an object in `runtime-dom/nodeOps` for operations (manipulations) that depend on the DOM.\n- Combine the factory function and `nodeOps` in `runtime-dom/index` to generate the renderer.\n\nThese are the concepts of \"DIP\" and \"DI\".\n\n<KawaikoNote variant=\"warning\" title=\"This is a bit tricky\">\n\nDI and DIP are among the more challenging design pattern concepts.\\\nIt's okay to just get a rough idea at first!\\\nAs you write more code, it will click: \"Ah, so that's what it means!\"\n\n</KawaikoNote>\n\nFirst, let's talk about DIP (Dependency Inversion Principle). By implementing an interface, we can invert the dependency. What you should pay attention to is the `RendererOptions` interface implemented in `renderer.ts`. Both the factory function and `nodeOps` should adhere to this `RendererOptions` interface (depend on the `RendererOptions` interface).\n\n<KawaikoNote variant=\"funny\" title=\"Think of it like cooking\">\n\nIf DIP were cooking...\\\nWith a \"recipe (interface)\", you can make the same dish whether ingredients are domestic or imported.\\\nThe renderer (chef) just follows \"RendererOptions (recipe)\" and doesn't need to worry about the actual ingredients (DOM ops or other ops).\n\n</KawaikoNote>\n\nBy using this, we perform DI. Dependency Injection (DI) is a technique that reduces dependency by injecting an object that an object depends on from the outside. In this case, the renderer depends on an object that implements `RendererOptions` (in this case, `nodeOps`). Instead of implementing this dependency directly from the renderer, we receive it as an argument to the factory. By using these techniques, we make sure that the renderer does not depend on the DOM.\n\n<KawaikoNote variant=\"base\" title=\"In summary\">\n\n**DIP**: Depend on interfaces (contracts), not concrete implementations\\\n**DI**: Receive dependencies from outside (inject them)\n\nCombining these two makes your code flexible and easier to test!\n\n</KawaikoNote>\n\nDI and DIP may be difficult concepts if you are not familiar with them, but they are important techniques that are often used, so I hope you can research and understand them on your own.\n\n### Completing createApp\n\nNow, let's get back to the implementation. Now that the renderer has been generated, all we need to do is consider the red area in the following diagram.\n\n![createAppAPI factory flow](/figures/10-minimum-example/package-architecture/create-app-api-factory.svg)\n\nHowever, it's a simple task. We just need to implement the factory function for createApp so that it can accept the renderer we created earlier.\n\n```ts\n// ~/packages/runtime-core apiCreateApp.ts\n\nimport { Component } from './component'\nimport { RootRenderFunction } from './renderer'\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void\n}\n\nexport type CreateAppFunction<HostElement> = (\n  rootComponent: Component,\n) => App<HostElement>\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const message = rootComponent.render!()\n        render(message, rootContainer)\n      },\n    }\n\n    return app\n  }\n}\n```\n\n```ts\n// ~/packages/runtime-dom/index.ts\n\nimport {\n  CreateAppFunction,\n  createAppAPI,\n  createRenderer,\n} from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\nconst _createApp = createAppAPI(render)\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args)\n  const { mount } = app\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector)\n    if (!container) return\n    mount(container)\n  }\n\n  return app\n}) as CreateAppFunction<Element>\n```\n\nI moved the types to `~/packages/runtime-core/component.ts`, but that's not important, so please refer to the source code (it's just aligning with the original Vue.js).\n\nNow that we are closer to the source code of the original Vue.js, let's test it. If the message is still displayed, it's OK.\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/015_package_architecture)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/020-simple-h-function.md",
    "content": "# Let's enable rendering HTML elements\n\n## What is the h function?\n\n<KawaikoNote variant=\"question\" title=\"What does 'h' stand for?\">\n\n`h` is short for `hyperscript`. Since it's a function that expresses\nHTML (Hyper Text Markup Language) in JavaScript, it got this name!\n\n</KawaikoNote>\n\nSo far, we have made the following source code work:\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\nThis is a function that simply renders \"Hello World.\" on the screen.  \nSince it's a bit lonely with just a message, let's think about a developer interface that can also render HTML elements.  \nThat's where the `h function` comes in. This `h` stands for `hyperscript` and is provided as a function for writing HTML (Hyper Text Markup Language) in JavaScript.\n\n> h() is short for hyperscript - which means \"JavaScript that produces HTML (hypertext markup language)\". This name is inherited from conventions shared by many Virtual DOM implementations. A more descriptive name could be createVnode(), but a shorter name helps when you have to call this function many times in a render function.\n\nQuote: https://vuejs.org/guide/extras/render-function.html#creating-vnodes\n\nLet's take a look at the h function in Vue.js.\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['HelloWorld']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nAs a basic usage of the h function, you specify the tag name as the first argument, attributes as the second argument, and an array of child elements as the third argument.  \nHere, I specifically mentioned \"basic usage\" because the h function actually has multiple syntaxes for its arguments, and you can omit the second argument or not use an array for child elements.  \nHowever, here we will implement it in the most basic syntax.\n\n## How should we implement it?\n\nNow that we understand the developer interface, let's decide how to implement it.  \nThe important point to note is how it is used as the return value of the render function.  \nThis means that the `h` function returns some kind of object and uses that result internally.\\\nSince it is difficult to understand with complex child elements, let's consider the result of implementing a simple h function.\n\n```ts\nconst result = h('div', { class: 'container' }, ['hello'])\n```\n\nWhat kind of result should be stored in `result`? (How should we format the result and how should we render it?)\n\nLet's assume that the following object is stored in `result`:\n\n```ts\nconst result = {\n  type: 'div',\n  props: { class: 'container' },\n  children: ['hello'],\n}\n```\n\nIn other words, we will receive an object similar to the one above from the render function and use it to perform DOM operations and render it.\\\nThe image is like this (inside the `mount` of `createApp`):\n\n```ts\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const node = rootComponent.render!()\n    render(node, rootContainer)\n  },\n}\n```\n\nWell, the only thing that has changed is that we changed the `message` string to an `node` object.  \nAll we have to do now is perform DOM operations based on the object in the render function.\n\nActually, this object has a name, \"Virtual DOM\".\nWe will explain more about the Virtual DOM in the Virtual DOM chapter, so for now, just remember the name.\n\n<KawaikoNote variant=\"funny\" title=\"The true nature of Virtual DOM\">\n\n\"Virtual DOM\" might sound complex, but it's just a JavaScript object!\nIt represents the DOM with a simple `{ type, props, children }` structure.\n\n</KawaikoNote>\n\n## Implementing the h function\n\nFirst, create the necessary files.\n\n```sh\npwd # ~\ntouch packages/runtime-core/vnode.ts\ntouch packages/runtime-core/h.ts\n```\n\nDefine the types in vnode.ts. This is all we will do in vnode.ts.\n\n```ts\nexport interface VNode {\n  type: string\n  props: VNodeProps\n  children: (VNode | string)[]\n}\n\nexport interface VNodeProps {\n  [key: string]: any\n}\n```\n\nNext, implement the function body in h.ts.\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return { type, props, children }\n}\n```\n\nFor now, let's try using the h function in the playground.\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\nThe display on the screen is broken, but if you add a log in apiCreateApp, you can see that it is working as expected.\n\n```ts\nmount(rootContainer: HostElement) {\n  const vnode = rootComponent.render!();\n  console.log(vnode); // Check the log\n  render(vnode, rootContainer);\n},\n```\n\nNow, let's implement the render function.\\\nImplement `createElement`, `createText`, and `insert` in RendererOptions.\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  createElement(type: string): HostNode // Added\n\n  createText(text: string): HostNode // Added\n\n  setElementText(node: HostNode, text: string): void\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void // Added\n}\n```\n\nImplement the `renderVNode` function in the render function. For now, we are ignoring the `props`.\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  const {\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options\n\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === 'string') return hostCreateText(vnode)\n    const el = hostCreateElement(vnode.type)\n\n    for (const child of vnode.children) {\n      const childEl = renderVNode(child)\n      hostInsert(childEl, el)\n    }\n\n    return el\n  }\n\n  const render: RootRenderFunction = (vnode, container) => {\n    const el = renderVNode(vnode)\n    hostInsert(el, container)\n  }\n\n  return { render }\n}\n```\n\nIn the nodeOps of runtime-dom, define the actual DOM operations.\n\n```ts\nexport const nodeOps: RendererOptions<Node> = {\n  // Added\n  createElement: tagName => {\n    return document.createElement(tagName)\n  },\n\n  // Added\n  createText: (text: string) => {\n    return document.createTextNode(text)\n  },\n\n  setElementText(node, text) {\n    node.textContent = text\n  },\n\n  // Added\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\nWell, at this point, you should be able to render elements on the screen.\\\nTry writing and testing various things in the playground!\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nYay! Now we can use the h function to render various tags!\n\n![VNode log from a simple h function](/figures/10-minimum-example/simple-h-function/basic-vnode-log.png)\n\n<KawaikoNote variant=\"surprise\" title=\"h function complete!\">\n\nNow you can express HTML in JavaScript!\nWith nested structures, you can create any complex UI.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/025-event-handler-and-attrs.md",
    "content": "# Let's work on supporting event handlers and attributes.\n\n<KawaikoNote variant=\"question\" title=\"What are props?\">\n\nProps is short for \"properties\" - information passed to elements.\nEvent handlers (like onClick) and attributes (like style, class) are all handled as props!\n\n</KawaikoNote>\n\n## Since it's lonely just to display\n\nSince we have the opportunity, let's implement props so that we can use click events and styles.\n\nRegarding this part, although it is okay to implement it directly in renderVNode, let's try to proceed while considering the design following the original.\n\nPlease pay attention to the runtime-dom directory of the original Vue.js.\n\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src\n\nWhat I want you to pay particular attention to is the `modules` directory and the `patchProp.ts` file.\n\nInside the modules directory, there are files for manipulating classes, styles, and other props.\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src/modules\n\nThese are all combined into a function called patchProp in patchProp.ts and mixed into nodeOps.\n\nInstead of explaining in words, I will try to do it based on this design.\n\n## Creating the framework for patchProps\n\nFirst, let's create the framework.\n\n```sh\npwd # ~\ntouch packages/runtime-dom/patchProp.ts\n```\n\nContents of `runtime-dom/patchProp.ts`\n\n```ts\ntype DOMRendererOptions = RendererOptions<Node, Element>\n\nconst onRE = /^on[^a-z]/\nexport const isOn = (key: string) => onRE.test(key)\n\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    // patchEvent(el, key, value); // We will implement this later\n  } else {\n    // patchAttr(el, key, value); // We will implement this later\n  }\n}\n```\n\nSince the type of patchProp is not defined in RendererOptions, let's define it.\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement\n> {\n  // Add\n  patchProp(el: HostElement, key: string, value: any): void;\n  .\n  .\n  .\n```\n\nWith this, we need to modify nodeOps to exclude parts other than patchProps.\n\n```ts\n// Omit patchProp\nexport const nodeOps: Omit<RendererOptions, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n  .\n  .\n  .\n```\n\nThen, when generating the renderer in `runtime-dom/index`, let's change it to pass patchProp together.\n\n```ts\nconst { render } = createRenderer({ ...nodeOps, patchProp })\n```\n\n## Event handlers\n\nLet's implement patchEvent.\n\n```sh\npwd # ~\nmkdir packages/runtime-dom/modules\ntouch packages/runtime-dom/modules/events.ts\n```\n\nImplement events.ts.\n\n```ts\ninterface Invoker extends EventListener {\n  value: EventValue\n}\n\ntype EventValue = Function\n\nexport function addEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.addEventListener(event, handler)\n}\n\nexport function removeEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.removeEventListener(event, handler)\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {})\n  const existingInvoker = invokers[rawName]\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value\n  } else {\n    const name = parseName(rawName)\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value))\n      addEventListener(el, name, invoker)\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker)\n      invokers[rawName] = undefined\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase()\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e)\n  }\n  invoker.value = initialValue\n  return invoker\n}\n```\n\nIt's a bit long, but if you split it, it's a very simple code.\n\naddEventListener is simply a function for registering event listeners as the name suggests.\\\nAlthough you actually need to remove it at the appropriate timing, we will ignore it for now.\n\nIn patchEvent, we wrap the listener with a function called invoker and register the listener.\\\nRegarding parseName, it simply converts prop key names such as `onClick` and `onInput` to lowercase by removing \"on\" (e.g. click, input).\nOne thing to note is that in order not to add duplicate addEventListeners to the same element, we add an invoker to the element with the name `_vei` (vue event invokers).\\\nBy updating existingInvoker.value at the time of patch, we can update the handler without adding duplicate addEventListeners.\\\nThe term \"invoker\" simply means \"one who executes.\" There is no deeper meaning; it is just an object that stores the handler that will actually be executed.\n\nNow let's incorporate it into patchProps and try using it in renderVNode.\n\npatchProps\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value)\n  } else {\n    // patchAttr(el, key, value); // We will implement this later\n  }\n}\n```\n\nrenderVNode in runtime-core/renderer.ts\n\n```ts\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options;\n  .\n  .\n  .\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === \"string\") return hostCreateText(vnode);\n    const el = hostCreateElement(vnode.type);\n\n    // Here\n    Object.entries(vnode.props).forEach(([key, value]) => {\n      hostPatchProp(el, key, value);\n    });\n    .\n    .\n    .\n```\n\nNow let's run it in the playground. I will try to display a simple alert.\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nWe can now register event handlers with the h function!\n\n![Event handler example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/event-handler-result.png)\n\n<KawaikoNote variant=\"funny\" title=\"The invoker trick\">\n\nIf you register event handlers directly, you need to remove/add them on every update.\nBy wrapping with an invoker, you can just swap the value instead!\n\n</KawaikoNote>\n\n## Trying to support other props\n\nAfter this, it's just a matter of doing the same thing with setAttribute.\\\nWe will implement this in `modules/attrs.ts`.\\\nI would like you to try it yourself. The answer will be attached at the end of this chapter in the source code, so please check it there.\\\nOnce you can make this code work, you have reached the goal.\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', { id: 'my-app' }, [\n      h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n![Attribute patching example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/attrs-result.png)\n\nNow we can handle a wide range of HTML!\n\n<KawaikoNote variant=\"surprise\" title=\"Props support complete!\">\n\nWith events and attributes supported, you can now create interactive UIs!\nThings are starting to feel like a real app.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system.md",
    "content": "# Prerequisite Knowledge for the Reactivity System\n\n## Developer interface we aim for this time\n\nFrom here, we will talk about the essence of Vue.js, which is the Reactivity System.\n\n<KawaikoNote variant=\"surprise\" title=\"Here we go!\">\n\nThis is the heart of Vue.js!\\\nOnce you understand the Reactivity System, you'll understand Vue.js's \"magic\".\\\nIt's a bit challenging, but let's tackle it together!\n\n</KawaikoNote>\n\nThe previous implementation, although it looks similar to Vue.js, is not actually Vue.js in terms of functionality.  \nI simply implemented the initial developer interface and made it possible to display various HTML.\n\nHowever, as it is, once the screen is rendered, it remains the same, and as a web application, it becomes just a static site.  \nFrom now on, we will add state to create a richer UI, and update the rendering when the state changes.\n\nFirst, let's think about what kind of developer interface it will be, as usual.  \nHow about something like this?\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n\n    const increment = () => {\n      state.count++\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nIf you are used to developing with Single File Components (SFC), this may look a little unfamiliar.  \nThis is a developer interface that uses the `setup` option to hold state and return a render function.  \nIn fact, Vue.js has such notation.\n\nhttps://vuejs.org/api/composition-api-setup.html#usage-with-render-functions\n\nWe define the state with the `reactive` function, implement a function called `increment` that modifies it, and bind it to the click event of the button.  \nTo summarize what we want to do:\n\n- Execute the `setup` function to obtain a function for obtaining the vnode from the return value\n- Make the object passed to the `reactive` function reactive\n- When the button is clicked, the state is updated\n- Track the state updates, re-execute the render function, and redraw the screen\n\n## What is the Reactivity System?\n\nNow, let's review what reactivity is.  \nLet's refer to the official documentation.\n\n> Reactive objects are JavaScript proxies that behave like normal objects. The difference is that Vue can track property access and changes on reactive objects.\n\n[Source](https://vuejs.org/guide/essentials/reactivity-fundamentals)\n\n> One of Vue's most distinctive features is its modest Reactivity System. The state of a component is composed of reactive JavaScript objects. When the state changes, the view is updated.\n\n[Source](https://vuejs.org/guide/extras/reactivity-in-depth.html)\n\nIn summary, \"reactive objects update the screen when there are changes\".  \nLet's put aside how to achieve this for now and implement the developer interface mentioned earlier.\n\n## Implementation of the setup function\n\nWhat we need to do is very simple.  \nWe receive the `setup` option and execute it, and then we can use it just like the previous `render` option.\n\nEdit `~/packages/runtime-core/componentOptions.ts`:\n\n```ts\nexport type ComponentOptions = {\n  render?: Function\n  setup?: () => Function // Added\n}\n```\n\nThen use it:\n\n```ts\n// createAppAPI\n\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    updateComponent()\n  },\n}\n```\n\n```ts\n// playground\n\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // Define state here in the future\n    // const state = reactive({ count: 0 })\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n        h(\n          'button',\n          {\n            onClick() {\n              alert('Hello world!')\n            },\n          },\n          ['click me!'],\n        ),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nWell, that's it.  \nActually, we want to execute this `updateComponent` when the state is changed.\n\n## Proxy Objects\n\nThis is the main theme for this time. I want to execute `updateComponent` when the state is changed somehow.\n\nThe key to this is an object called Proxy.\n\n<KawaikoNote variant=\"question\" title=\"What's Proxy?\">\n\nProxy is a standard JavaScript feature, not something Vue.js invented.\\\nThink of it as \"a mechanism to monitor and customize access to objects\"!\\\nWith this, we can detect when \"a value was read\" or \"a value was changed\".\n\n</KawaikoNote>\n\nFirst, let me explain about each of them, not about the implementation method of the Reactivity System.\n\nhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy\n\nProxy is a very interesting object.\n\nYou can use it by passing an object as an argument and using `new` like this:\n\n```ts\nconst o = new Proxy({ value: 1 }, {})\nconsole.log(o.value) // 1\n```\n\nIn this example, `o` behaves almost the same as a normal object.\n\nNow, what's interesting is that Proxy can take a second argument and register a handler.\nThis handler is a handler for object operations. Please take a look at the following example:\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n\n  {\n    get(target, key, receiver) {\n      console.log(`target:${target}, key: ${key}`)\n      return target[key]\n    },\n  },\n)\n```\n\nIn this example, we are writing settings for the generated object.\nSpecifically, when accessing (get) the properties of this object, the original object (target) and the accessed key name will be output to the console.\nLet's check the operation in a browser or something.\n\n![Proxy get trap console output](/figures/10-minimum-example/reactivity/proxy-get-trap.png)\n\nYou can see that the set processing set for reading the value from the property of the object generated by this Proxy is being executed.\n\nSimilarly, you can also set it for set.\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n  {\n    set(target, key, value, receiver) {\n      console.log('hello from setter')\n      target[key] = value\n      return true\n    },\n  },\n)\n```\n\n![Proxy set trap console output](/figures/10-minimum-example/reactivity/proxy-set-trap.png)\n\n<KawaikoNote variant=\"funny\" title=\"This is the secret of reactivity!\">\n\nWe can detect \"reads\" with get and \"writes\" with set...\\\nSo if we call \"screen update logic\" at set timing, we achieve the magic of **automatic screen updates when values change**!\n\n</KawaikoNote>\n\nThis is the extent of understanding Proxy.\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/035-try-implementing-a-minimum-reactivity-system.md",
    "content": "# Try Implementing a Small Reactivity System\n\n## Reactivity Mechanism Using Proxy\n\n::: info Differences in Design Compared to the Current `vuejs/core`\nAs of December 2024, Vue.js's Reactivity system employs a doubly linked list-based Observer Pattern.\\\nThis implementation, introduced in [Refactor reactivity system to use version counting and doubly-linked list tracking](https://github.com/vuejs/core/pull/10397), has contributed significantly to performance improvements.  \n\nHowever, for those implementing a reactivity system for the first time, this can be somewhat challenging to grasp. In this chapter, we will create a simplified implementation of the traditional (pre-optimization) system.\\\nFor a more detailed explanation of a system closer to the current implementation, please refer to [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization).  \n\nAnother significant improvement, [feat(reactivity): more efficient reactivity system](https://github.com/vuejs/core/pull/5912), will be covered in a separate chapter.  \n:::\n\nTo clarify the purpose again, the purpose this time is to \"execute `updateComponent` when the state is changed\". Let me explain the implementation process using Proxy.\n\nFirst, Vue.js's Reactivity System involves `target`, `Proxy`, `ReactiveEffect`, `Dep`, `track`, `trigger`, `targetMap`, and `activeEffect` (currently `activeSub`).\n\n<KawaikoNote variant=\"warning\" title=\"Lots of characters!\">\n\nMany terms suddenly appeared, but don't panic!\\\nIf we look at each role one by one, the puzzle pieces will fit together.\\\nFirst, let's aim to \"roughly grasp the big picture\".\n\n</KawaikoNote>\n\nFirst, let's talk about the structure of targetMap.\ntargetMap is a mapping of keys and deps for a certain target.\nTarget refers to the object you want to make reactive, and dep refers to the effect (function) you want to execute. You can think of it that way.\nIn code, it looks like this:\n\n```ts\ntype Target = any // any target\ntype TargetKey = any // any key that the target has\n\nconst targetMap = new WeakMap<Target, KeyToDepMap>() // defined as a global variable in this module\n\ntype KeyToDepMap = Map<TargetKey, Dep> // a map of target's key and effect\n\ntype Dep = Set<ReactiveEffect> // dep has multiple ReactiveEffects\n\nclass ReactiveEffect {\n  constructor(\n    // here, you give the function you want to actually apply as an effect (in this case, updateComponent)\n    public fn: () => T,\n  ) {}\n}\n```\n\nIt means registering \"a certain effect\" for \"a certain key\" of \"a certain target (object)\".\n\nIt might be hard to understand just by looking at the code, so here is a concrete example and a supplementary diagram.\\\nConsider a component like the following:\n\n```ts\nexport default defineComponent({\n  setup() {\n    const state1 = reactive({ name: \"John\", age: 20 })\n    const state2 = reactive({ count: 0 })\n\n    function onCountUpdated() {\n      console.log(\"count updated\")\n    }\n\n    watch(() => state2.count, onCountUpdated)\n\n    return () => h(\"p\", {}, `name: ${state1.name}`)\n  }\n})\n```\n\nAlthough we haven't implemented `watch` in this chapter yet, it is written here for the sake of illustration.\\\nIn this component, the targetMap will eventually be formed as follows.\n\n![targetMap structure](/figures/10-minimum-example/reactivity/target-map-structure.svg)\n\nThe key of targetMap is \"a certain target\". In this example, state1 and state2 correspond to that.\\\nThe keys that these targets have become the keys of targetMap.\\\nThe effects associated with them become the values.\n\nIn the part `() => h(\"p\", {}, name: ${state1.name})`, the mapping `state1->name->updateComponentFn` is registered, and in the part `watch(() => state2.count, onCountUpdated)`, the mapping `state2->count->onCountUpdated` is registered.\n\nThis basic structure is responsible for the rest, and then we think about how to create (register) targetMap and how to execute the effect.\n\n<KawaikoNote variant=\"funny\" title=\"Think simply\">\n\n**targetMap** is a notebook that records \"who affects whom\".\\\nWhen `state1.name` changes → run `updateComponent`\\\nWhen `state2.count` changes → run `onCountUpdated`\\\nIt records these relationships!\n\n</KawaikoNote>\n\nThat's where the concepts of `track` and `trigger` come in.\nAs the names suggest, `track` is a function that registers in `targetMap`, and `trigger` is a function that retrieves the effect from `targetMap` and executes it.\n\n```ts\nexport function track(target: object, key: unknown) {\n  // ..\n}\n\nexport function trigger(target: object, key?: unknown) {\n  // ..\n}\n```\n\nAnd these `track` and `trigger` are implemented in the get and set handlers of Proxy.\n\n```ts\nconst state = new Proxy(\n  { count: 1 },\n  {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  },\n)\n```\n\nThe API for generating this Proxy is the reactive function.\n\n```ts\nfunction reactive<T>(target: T) {\n  return new Proxy(target, {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n![reactive track and trigger flow](/figures/10-minimum-example/reactivity/reactive-track-trigger.svg)\n\n<KawaikoNote variant=\"base\" title=\"Key points so far\">\n\n- **track**: Called when \"reading\" a value, registers \"run X when this value changes\"\n- **trigger**: Called when \"writing\" a value, executes registered handlers\n- **reactive**: A function that creates a Proxy with this mechanism\n\n</KawaikoNote>\n\nHere, you may notice one missing element. That is, \"which function to register in track?\".\nThe answer is the concept of `activeEffect`.\nThis is also defined as a global variable in this module, just like targetMap, and is set in the `run` method of ReactiveEffect.\n\n```ts\nlet activeEffect: ReactiveEffect | undefined\n\nclass ReactiveEffect {\n  constructor(\n    // here, you give the function you want to actually apply as an effect (in this case, updateComponent)\n    public fn: () => T,\n  ) {}\n\n  run() {\n    activeEffect = this\n    return this.fn()\n  }\n}\n```\n\nTo understand how it works, imagine a component like this.\n\n```ts\n{\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return function render() {\n      return h(\"div\", { id: \"my-app\" }, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\n          \"button\",\n          {\n            onClick: increment,\n          },\n          [\"increment\"]\n        ),\n      ]);\n    };\n  },\n}\n```\n\nInternally, this is how reactivity is formed.\n\n```ts\n// Implementation inside chibivue\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    const effect = new ReactiveEffect(updateComponent)\n    effect.run()\n  },\n}\n```\n\nTo explain step by step, first, the `setup` function is executed.\\\nA reactive proxy is generated at this point. In other words, any operation performed on the proxy created here will behave as configured in the proxy.\n\n```ts\nconst state = reactive({ count: 0 }) // Generating a proxy\n```\n\nNext, we pass `updateComponent` to create a `ReactiveEffect` (Observer side).\n\n```ts\nconst effect = new ReactiveEffect(updateComponent)\n```\n\nThe `componentRender` used in `updateComponent` is a function that is the `return value` of `setup`, and this function references the object created by the proxy.\n\n```ts\nfunction render() {\n  return h('div', { id: 'my-app' }, [\n    h('p', {}, [`count: ${state.count}`]), // Referencing the object created by the proxy\n    h(\n      'button',\n      {\n        onClick: increment,\n      },\n      ['increment'],\n    ),\n  ])\n}\n```\n\nWhen this function is actually executed, the `getter` function of `state.count` is executed, and `track` is triggered. \nIn this situation, let's execute the effect.\n\n```ts\neffect.run()\n```\n\nThen, `updateComponent` (a ReactiveEffect with `updateComponent`) is set to `activeEffect`. \nWhen `track` is triggered in this state, a map of `state.count` and `updateComponent` (a ReactiveEffect with `updateComponent`) is registered in `targetMap`. \nThis is how reactivity is formed.\n\nNow, let's consider what happens when `increment` is executed. \nSince `increment` is rewriting `state.count`, the `setter` is executed, and `trigger` is triggered. \n`trigger` finds and executes the `effect` (in this case, updateComponent) from `targetMap` based on `state` and `count`. \nThis is how the screen update is triggered!\n\nThis allows us to achieve reactivity.\n\nIt's a bit complicated, so let's summarize it in a diagram.\n\n![Reactivity setup flow during mount](/figures/10-minimum-example/reactivity/reactivity-setup-flow.svg)\n\n## Based on these, let's implement it.\n\nThe most difficult part is understanding everything up to this point, so once you understand it, all you have to do is write the source code.\nHowever, even if you understand only the above, there may be some people who cannot understand it without knowing what is actually happening.\nFor those people, let's try implementing it here first. Then, while reading the actual code, please refer back to the previous section!\n\nFirst, let's create the necessary files. We will create them in `packages/reactivity`.\nHere, we will try to be conscious of the configuration of the original Vue as much as possible.\n\n```sh\npwd # ~\nmkdir packages/reactivity\n\ntouch packages/reactivity/index.ts\n\ntouch packages/reactivity/dep.ts\ntouch packages/reactivity/effect.ts\ntouch packages/reactivity/reactive.ts\ntouch packages/reactivity/baseHandler.ts\n```\n\nAs usual, `index.ts` just exports, so I won't explain it in detail. Export what you want to use from the reactivity external package here.\n\nNext is `dep.ts`.\n\n```ts\nimport { type ReactiveEffect } from './effect'\n\nexport type Dep = Set<ReactiveEffect>\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects)\n  return dep\n}\n```\n\nThere is no definition of `effect` yet, but we will implement it later, so it's okay.\n\nNext is `effect.ts`.\n\n```ts\nimport { Dep, createDep } from './dep'\n\ntype KeyToDepMap = Map<any, Dep>\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nexport let activeEffect: ReactiveEffect | undefined\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    // ※ Save the activeEffect before executing fn and restore it after execution.\n    // If you don't do this, it will be overwritten one after another and behave unexpectedly. (Let's restore it to its original state when you're done)\n    let parent: ReactiveEffect | undefined = activeEffect\n    activeEffect = this\n    const res = this.fn()\n    activeEffect = parent\n    return res\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target)\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()))\n  }\n\n  let dep = depsMap.get(key)\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()))\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect)\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  const dep = depsMap.get(key)\n\n  if (dep) {\n    const effects = [...dep]\n    for (const effect of effects) {\n      effect.run()\n    }\n  }\n}\n```\n\nI haven't explained the contents of `track` and `trigger` so far, but they simply register and retrieve from `targetMap` and execute them, so please try to read them carefully.\n\nNext is `baseHandler.ts`. Here, we define the handler for the reactive proxy.\nWell, you can implement it directly in `reactive`, but I followed the original Vue because it is like this.\nIn reality, there are various proxies such as `readonly` and `shallow`, so the idea is to implement the handlers for those proxies here. (We won't do it this time, though)\n\n```ts\nimport { track, trigger } from './effect'\nimport { reactive } from './reactive'\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key)\n\n    const res = Reflect.get(target, key, receiver)\n    // If it is an object, make it reactive (this allows nested objects to be reactive as well).\n    if (res !== null && typeof res === 'object') {\n      return reactive(res)\n    }\n\n    return res\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key]\n    Reflect.set(target, key, value, receiver)\n    // check if the value has changed\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key)\n    }\n    return true\n  },\n}\n\nconst hasChanged = (value: any, oldValue: any): boolean =>\n  !Object.is(value, oldValue)\n```\n\nHere, `Reflect` appears, which is similar to `Proxy`, but while `Proxy` is for writing meta settings for objects, `Reflect` is for performing operations on existing objects.\nBoth `Proxy` and `Reflect` are APIs for meta programming related to objects in the JS engine, and they allow you to perform meta operations compared to using objects normally.\nYou can execute functions that change the object, execute functions that read the object, and check if a key exists, and perform various meta operations.\nFor now, it's okay to understand that `Proxy` is for meta settings at the stage of creating an object, and `Reflect` is for meta operations on existing objects.\n\nNext is `reactive.ts`.\n\n```ts\nimport { mutableHandlers } from './baseHandler'\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\nNow that the implementation of `reactive` is complete, let's try using them when mounting.\n`~/packages/runtime-core/apiCreateApp.ts`.\n\n```ts\nimport { ReactiveEffect } from '../reactivity'\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const componentRender = rootComponent.setup!()\n\n        const updateComponent = () => {\n          const vnode = componentRender()\n          render(vnode, rootContainer)\n        }\n\n        // From here\n        const effect = new ReactiveEffect(updateComponent)\n        effect.run()\n        // To here\n      },\n    }\n\n    return app\n  }\n}\n```\n\nNow, let's try it in the playground.\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nOops...\n\nThe rendering is working fine now, but something seems off.\nWell, it's not surprising because in `updateComponent`, we create elements every time.\nSo, let's remove all the elements before each rendering.\n\n![Reactive example mistake in the browser](/figures/10-minimum-example/reactivity/reactive-example-mistake.png)\n\nModify the `render` function in `~/packages/runtime-core/renderer.ts` like this:\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild) // Add code to remove all elements\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\nNow, how about this?\n\n![Reactive example rendered in the browser](/figures/10-minimum-example/reactivity/reactive-example-result.png)\n\nNow it seems to be working fine!\n\nNow we can update the screen with `reactive`!\n\n<KawaikoNote variant=\"surprise\" title=\"Congratulations!\">\n\nThe basics of the Reactivity System are complete!\\\nDo you understand the secret behind Vue.js's \"magic\" of automatic screen updates?\\\nBy overcoming this, you now have a deep understanding of Vue.js internals!\n\n</KawaikoNote>\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/030_reactive_system)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/040-minimum-virtual-dom.md",
    "content": "# Minimum Virtual DOM\n\n## What is Virtual DOM used for?\n\n<KawaikoNote variant=\"question\" title=\"Why Virtual DOM?\">\n\nThe purpose of Virtual DOM is \"diff updates\".\nIt identifies the parts that have changed and performs only necessary DOM operations!\n(However, Virtual DOM itself has overhead, so it's not a silver bullet)\n\n</KawaikoNote>\n\nBy introducing the Reactivity System in the previous chapter, we were able to dynamically update the screen. Let's take a look at the content of the current render function again.\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild)\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\nSome people may have noticed in the previous chapter that there is a lot of waste in this function.\n\nTake a look at the playground.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n```\n\nThe problem is that only the part that changes when increment is executed is the `count: ${state.count}` part, but in renderVNode, all the DOM elements are removed and recreated from scratch. This feels very wasteful. \\\nAlthough it seems to be working fine for now because it is still small, you can easily imagine that performance will be greatly reduced if you have to recreate a complex DOM from scratch every time you develop a web application. \\\nTherefore, since we already have a Virtual DOM, we want to implement an implementation that compares the current Virtual DOM with the previous one and only updates the parts where there are differences using DOM operations. \\\nNow, this is the main theme of this chapter.\n\nLet's see what we want to do in the source code. When we have a component like the one above, the return value of the render function becomes a Virtual DOM like the following. At the time of the initial rendering, the count is 0, so it looks like this:\n\n```ts\nconst vnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 0`]\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      [\"increment\"]\n    }\n  ]\n}\n```\n\nLet's keep this vnode and have another vnode for the next rendering. The following is the vnode when the first button is clicked:\n\n```ts\nconst nextVnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 1`] // Only want to update this part\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      [\"increment\"]\n    }\n  ]\n}\n```\n\nNow, with these two vnodes, the screen is in the state of vnode (before it becomes nextVnode). \\\nWe want to pass these two to a function called patch and render only the differences.\n\n```ts\nconst vnode = {...}\nconst nextVnode = {...}\npatch(vnode, nextVnode, container)\n```\n\nI introduced the function name earlier, but this differential rendering is called \"patch\". \\\nIt is also sometimes called \"reconciliation\". By using these two Virtual DOMs, you can efficiently update the screen.\n\n## Before implementing the patch function\n\nThis is not directly related to the main topic, but let's do a slight refactoring here (because it is convenient for what we are going to talk about next). \\\nLet's create a function called createVNode in vnode.ts and make h function call it.\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: unknown,\n): VNode {\n  const vnode: VNode = { type, props, children: [] }\n  return vnode\n}\n```\n\nChange h function as well.\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return createVNode(type, props, children)\n}\n```\n\nNow, let's get to the main point. Until now, the type of the small element that VNode has has been `(Vnode | string)[]`, but it is not enough to treat Text as a string, so let's try to unify it as VNode. \\\nText is not just a string, but it exists as an HTML TextElement, so it contains more information than just a string. \\\nWe want to treat it as a VNode in order to handle the surrounding information. \\\nSpecifically, let's use the symbol Text to have it as the type of VNode. \\\nFor example, when there is a text like `\"hello\"`,\n\n```ts\n{\n  type: Text,\n  props: null,\n  children: \"hello\"\n}\n```\n\nis the representation.\n\nAlso, one thing to note here is that when h function is executed, we will continue to use the conventional expression, and we will convert it by applying a function called normalize in the render function to represent Text as mentioned above. This is done to match the original Vue.js.\n\n`~/packages/runtime-core/vnode.ts`;\n\n```ts\nexport const Text = Symbol();\n\nexport type VNodeTypes = string | typeof Text;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\n// Type after normalization\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(..){..} // omitted\n\n// Implement the normalize function (used in renderer.ts)\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    // Convert string to the desired form introduced earlier\n    return createVNode(Text, null, String(child));\n  }\n}\n```\n\nNow Text can be treated as a VNode.\n\n## Design of the patch function\n\nFirst, let's take a look at the design of the patch function in the codebase. \\\n(We don't need to implement it here, just understand it.) \\\nThe patch function compares two vnodes, vnode1 and vnode2. However, vnode1 does not exist initially. \\\nTherefore, the patch function is divided into two processes: \"initial (generating dom from vnode2)\" and \"updating the difference between vnode1 and vnode2\". \\\nThese processes are named \"mount\" and \"patch\" respectively. \\\nAnd they are performed separately for ElementNode and TextNode (combined as \"process\" with the name \"mount\" and \"patch\" for each).\n\n<img   \n    src=\"/figures/10-minimum-example/virtual-dom/patch-function-architecture.svg\"\n    alt=\"Patch Function Architecture\"   \n    style=\"background-color: white;\"\n/>\n\n```ts\nconst patch = (\n  n1: VNode | string | null,\n  n2: VNode | string,\n  container: HostElement,\n) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else {\n    processElement(n1, n2, container)\n  }\n}\n\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: HostElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    patchElement(n1, n2)\n  }\n}\n\nconst processText = (n1: string | null, n2: string, container: HostElement) => {\n  if (n1 == null) {\n    mountText(n2, container)\n  } else {\n    patchText(n1, n2)\n  }\n}\n```\n\n## Actual implementation\n\nNow let's actually implement the patch function for the Virtual DOM. \\\nFirst, we want to have a reference to the actual DOM in the vnode when it is mounted, whether it is an Element or a Text.\\\nSo we add the \"el\" property to the vnode.\n\n`~/packages/runtime-core/vnode.ts`\n\n```ts\nexport interface VNode<HostNode = RendererNode> {\n  type: VNodeTypes\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined // [!code ++]\n}\n```\n\nNow let's move on to `~/packages/runtime-core/renderer.ts`. \\\nWe will implement it inside the `createRenderer` function and remove the `renderVNode` function.\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  // .\n  // .\n  // .\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2\n    if (type === Text) {\n      // processText(n1, n2, container);\n    } else {\n      // processElement(n1, n2, container);\n    }\n  }\n}\n```\n\nLet's start implementing from `processElement` and `mountElement`.\n\n```ts\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    // patchElement(n1, n2);\n  }\n}\n\nconst mountElement = (vnode: VNode, container: RendererElement) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children, el) // TODO:\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\nSince it is an element, we also need to mount its children. \\\nLet's use the `normalize` function we created earlier.\n\n```ts\nconst mountChildren = (children: VNode[], container: RendererElement) => {\n  for (let i = 0; i < children.length; i++) {\n    const child = (children[i] = normalizeVNode(children[i]))\n    patch(null, child, container)\n  }\n}\n```\n\nWith this, we have implemented the mounting of elements. \\\nNext, let's move on to mounting Text. \\\nHowever, this is just a simple DOM operation. \\\nIn the design explanation, we divided it into `mountText` and `patchText` functions, but since there is not much processing and it is not expected to become more complex in the future, let's write it directly.\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // TODO: patch\n  }\n}\n```\n\nNow, with the mounting of the initial render completed, let's move some of the processing from the `mount` function in `createAppAPI` to the `render` function so that we can hold two vnodes. \\\nSpecifically, we pass `rootComponent` to the `render` function and perform ReactiveEffect registration inside it.\n\n```ts\nreturn function createApp(rootComponent) {\n  const app: App = {\n    mount(rootContainer: HostElement) {\n      // Just pass rootComponent\n      render(rootComponent, rootContainer)\n    },\n  }\n}\n```\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\nNow, let's try to render in the playground to see if it works!\n\nSince we haven't implemented the patch function yet, the screen won't be updated.\n\nSo, let's continue writing the patch function.\n\n```ts\nconst patchElement = (n1: VNode, n2: VNode) => {\n  const el = (n2.el = n1.el!)\n\n  const props = n2.props\n\n  patchChildren(n1, n2, el)\n\n  for (const key in props) {\n    if (props[key] !== n1.props?.[key]) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n}\n\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\nThe same goes for Text nodes.\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // Add patch logic\n    const el = (n2.el = n1.el!)\n    if (n2.children !== n1.children) {\n      hostSetText(el, n2.children as string)\n    }\n  }\n}\n```\n\n※ Regarding patchChildren, normally we need to handle dynamic-length child elements by adding key attributes, but since we are implementing a small Virtual DOM, we won't cover the practicality of that here. \\\nIf you are interested, please refer to the Basic Virtual DOM section. \\\nHere, we aim to understand the implementation and role of Virtual DOM up to a certain extent.\n\nNow that we can perform diff rendering, let's take a look at the playground.\n\n![patch rendering result in the browser](/figures/10-minimum-example/virtual-dom/patch-rendering-result.png)\n\nWe have successfully implemented patching using Virtual DOM!!!!! Congratulations!\n\n<KawaikoNote variant=\"surprise\" title=\"Virtual DOM complete!\">\n\nThe diff detection mechanism is now implemented! This is the core technology of frameworks.\nExperience how the \"heart of the framework\" works with just a few hundred lines of code!\n\n</KawaikoNote>\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/040_vdom_system)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/050-minimum-component.md",
    "content": "# I want to develop using a component-based approach.\n\n<KawaikoNote variant=\"question\" title=\"Why components?\">\n\nComponents are a mechanism to divide UI into reusable parts.\nInstead of writing the same button multiple times, you define it once and reuse it!\n\n</KawaikoNote>\n\n## Thinking based on organizing existing implementations\n\nSo far, we have implemented createApp API, Reactivity System, and Virtual DOM system in a small scale.\\\nWith the current implementation, we can dynamically change the UI using the Reactivity System and perform diff rendering using the Virtual DOM system. \\\nHowever, as a developer interface, everything is written in createAppAPI.\\\nIn reality, I want to divide the files more and implement generic components for reusability.\\\nFirst, let's review the parts that are currently messy in the existing implementation. Please take a look at the render function in renderer.ts.\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n  let n2: VNode = null!\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\nIn the render function, information about the root component is directly defined.\\\nIn reality, n1, n2, updateComponent, and effect exist for each component.\\\nIn fact, from now on, I want to define the component (in a sense, the constructor) on the user side and instantiate it.\\\nAnd I want the instance to have properties such as n1, n2, and updateComponent.\\\nSo, let's think about encapsulating these as a component instance.\n\nLet's define something called `ComponentInternalInstance` in `~/packages/runtime-core/component.ts`. \\\nThis will be the type of the instance.\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component // The original user-defined component (old rootComponent (actually not just the root component))\n  vnode: VNode // To be explained later\n  subTree: VNode // Old n1\n  next: VNode | null // Old n2\n  effect: ReactiveEffect // Old effect\n  render: InternalRenderFunction // Old componentRender\n  update: () => void // Old updateComponent\n  isMounted: boolean\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild\n}\n```\n\nThe vnode, subTree, and next properties that this instance has are a bit complicated, but from now on, we will implement it so that ConcreteComponent can be specified as the type of VNode.\\\nIn instance.vnode, we will keep the VNode itself.\\\nAnd subTree and next will hold the rendering result VNode of that component. (This is the same as before with n1 and n2)\n\nIn terms of image,\n\n```ts\nconst MyComponent = {\n  setup() {\n    return h('p', {}, ['hello'])\n  },\n}\n\nconst App = {\n  setup() {\n    return h(MyComponent, {}, [])\n  },\n}\n```\n\nYou can use it like this, and if you let the instance be instance of MyComponent, instance.vnode will hold the result of `h(MyComponent, {}, [])`, and instance.subTree will hold the result of `h(\"p\", {}, [\"hello\"])`.\n\nFor now, let's implement it so that you can specify a component as the first argument of the h function.\\\nHowever, it's just a matter of receiving an object that defines the component as the type.\\\nIn `~/packages/runtime-core/vnode.ts`\n\n```ts\nexport type VNodeTypes = string | typeof Text | object // Add object;\n```\n\nIn `~/packages/runtime-core/h.ts`\n\n```ts\nexport function h(\n  type: string | object, // Add object\n  props: VNodeProps\n) {..}\n```\n\nLet's also make sure that VNode has a component instance.\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  component: ComponentInternalInstance | null // Add\n}\n```\n\nAs a result, the renderer also needs to handle components. \\\nImplement `processComponent` similar to `processElement` and `processText` for handling components, and also implement `mountComponent` and `patchComponent` (or `updateComponent`).\n\nFirst, let's start with an overview and detailed explanation.\n\n![Component instance flow](/figures/10-minimum-example/minimum-component/component-instance-flow.svg)\n\n```ts\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    // Add branching\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountComponent(n2, container)\n  } else {\n    updateComponent(n1, n2)\n  }\n}\n\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // TODO:\n}\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  // TODO:\n}\n```\n\nNow, let's take a look at `mountComponent`. There are three things to do.\n\n1. Create an instance of the component.\n2. Execute the `setup` function and store the result in the instance.\n3. Create a `ReactiveEffect` and store it in the instance.\n\nFirst, let's implement a function in `component.ts` to create an instance of the component (similar to a constructor).\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    type,\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n    isMounted: false,\n  }\n\n  return instance\n}\n```\n\nAlthough the type of each property is non-null, we initialize them with null when creating the instance (following the design of the original Vue.js).\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n  // TODO: setup component\n  // TODO: setup effect\n}\n```\n\nNext is the `setup` function. \\\nWe need to move the code that was previously written directly in the `render` function to here and store the result in the instance instead of using variables.\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  // TODO: setup effect\n}\n```\n\nFinally, let's combine the code for creating the effect into a function called `setupRenderEffect`. \\\nAgain, the main task is to move the code that was previously implemented directly in the `render` function to here, while utilizing the state of the instance.\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render } = instance\n\n    if (!instance.isMounted) {\n      // mount process\n      const subTree = (instance.subTree = normalizeVNode(render()))\n      patch(null, subTree, container)\n      initialVNode.el = subTree.el\n      instance.isMounted = true\n    } else {\n      // patch process\n      let { next, vnode } = instance\n\n      if (next) {\n        next.el = vnode.el\n        next.component = instance\n        instance.vnode = next\n        instance.next = null\n      } else {\n        next = vnode\n      }\n\n      const prevTree = instance.subTree\n      const nextTree = normalizeVNode(render())\n      instance.subTree = nextTree\n\n      patch(prevTree, nextTree, hostParentNode(prevTree.el!)!) // ※ 1\n      next.el = nextTree.el\n    }\n  }\n\n  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn))\n  const update = (instance.update = () => effect.run()) // Register to instance.update\n  update()\n}\n```\n\n※ 1: Please implement a function called `parentNode` in `nodeOps` that retrieves the parent Node.\n\n```ts\nparentNode: (node) => {\n    return node.parentNode;\n},\n```\n\nI think it's not particularly difficult, although it's a bit long.\\\nIn the `setupRenderEffect` function, a function for updating is registered as the `update` method of the instance, so in `updateComponent`, we just need to call that function.\n\n```ts\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!\n  instance.next = n2\n  instance.update()\n}\n```\n\nFinally, since the implementation that was defined in the `render` function so far is no longer needed, we will remove it.\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  patch(null, vnode, container)\n}\n```\n\nNow we can render components. Let's try creating a `playground` component as an example.\nIn this way, we can divide the rendering into components.\n\n<KawaikoNote variant=\"funny\" title=\"Role of instances\">\n\nThe component \"blueprint\" and \"instance\" are different things.\nEven if you line up the same CounterComponent 3 times, each one has its own independent state!\n\n</KawaikoNote>\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst CounterComponent = {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n<KawaikoNote variant=\"surprise\" title=\"Component system complete!\">\n\nNow you can componentize UI!\nThis mechanism is essential in real app development.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/051-component-props.md",
    "content": "# Component Props\n\n## Developer Interface\n\nLet's start with props.\\\nLet's think about the final developer interface.\\\nLet's consider that props are passed as the first argument to the `setup` function.\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\n## Implementation\n\nBased on this, let's think about the information we want to have in `ComponentInternalInstance`.\\\nWe need the definition of props specified as `props: { message: { type: String } }`, and a property to actually hold the props value, so we add the following:\n\n```ts\nexport type Data = Record<string, unknown>\n\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  propsOptions: Props // Holds an object like `props: { message: { type: String } }`\n\n  props: Data // Holds the actual data passed from the parent (in this case, it will be something like `{ message: \"hello\" }`)\n}\n```\n\nCreate a new file called `~/packages/runtime-core/componentProps.ts` with the following content:\n\n```ts\nexport type Props = Record<string, PropOptions | null>\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null\n  required?: boolean\n  default?: null | undefined | object\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} }\n```\n\nAdd it to the options when implementing the component.\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any> // Added\n  setup?: () => Function\n  render?: Function\n}\n```\n\nWhen generating an instance with `createComponentInstance`, set the propsOptions to the instance when generating the instance.\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    propsOptions: type.props || {},\n    props: {},\n```\n\nLet's think about how to form the `instance.props`.\\\nAt the time of component mounting, filter the props held by the vnode based on the propsOptions.\\\nConvert the filtered object into a reactive object using the `reactive` function, and assign it to `instance.props`.\n\nImplement a function called `initProps` in `componentProps.ts` that performs this series of steps.\n\n```ts\nexport function initProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const props: Data = {}\n  setFullProps(instance, rawProps, props)\n  instance.props = reactive(props)\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      if (options && options.hasOwnProperty(key)) {\n        props[key] = value\n      }\n    }\n  }\n}\n```\n\nActually execute `initProps` at the time of mounting, and pass props to the `setup` function as an argument.\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    // init props\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup(\n        instance.props // Pass props to setup\n      ) as InternalRenderFunction;\n    }\n    // .\n    // .\n    // .\n}\n```\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (props: Record<string, any>) => Function // Receive props\n  render?: Function\n}\n```\n\nAt this point, props should be passed to the child component, so let's check it in the playground.\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\nHowever, this is not enough, as the rendering is not updated when props are changed.\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```\n\nTo make this component work, we need to implement `updateProps` in `componentProps.ts` and execute it when the component updates.\n\n`~/packages/runtime-core/componentProps.ts`\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  Object.assign(props, rawProps)\n}\n```\n\nLet's organize the flow of component update processing.\\\nWhen a parent component re-renders, the props passed to child components may change.\\\nThe flow is as follows:\n\n1. The parent component's `render` function is executed, generating a new VNode for the child component\n2. In the `patch` process, `processComponent` is called, comparing the existing component (`n1`) with the new VNode (`n2`)\n3. If an existing component exists, the `updateComponent` function is called\n\nFirst, add the `next` property to `ComponentInternalInstance`.\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  vnode: VNode // Current VNode\n  next: VNode | null // When there's an update request from parent, the new VNode is set here\n  // .\n  // .\n}\n```\n\nNext, implement the update processing for already mounted components in `processComponent`.\n\n```ts\nconst processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  if (n1 == null) {\n    mountComponent(n2, container);\n  } else {\n    updateComponent(n1, n2); // Added\n  }\n};\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!; // Inherit the instance reference from old VNode to new VNode\n  instance.next = n2; // Set the new VNode to next\n  instance.update(); // Trigger component update\n};\n```\n\nIn `updateComponent`, we set the new VNode (`n2`) to `instance.next` and then call `instance.update()`.\\\nThis triggers the execution of `componentUpdateFn`.\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          // When there's an update request from parent (e.g., props changed)\n          next.el = vnode.el; // Inherit the current DOM element reference to the new VNode\n          next.component = instance; // Set the instance reference to the new VNode\n          instance.vnode = next; // Switch the instance's \"current VNode\" to the new one\n          instance.next = null; // Reset to null as it's been processed\n          updateProps(instance, next.props); // Update the instance's props with the new props\n        }\n        // If next doesn't exist, it's a re-render due to changes in the component's own reactive state\n```\n\nWhen `instance.next` exists, it means there was an update request from the parent component (such as props changes).\\\nIn this case, we reflect the new VNode's information to the instance before updating the props.\\\nWhen `instance.next` doesn't exist, it's a re-render due to changes in the component's own internal state (reactive values).\n\nIf the screen is updated, it's OK.\\\nNow, you can pass data to the component using props! Great job!\n\n![Component props flow in the browser](/figures/10-minimum-example/component-props/props-flow.png)\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system2)\n\nAs a side note, although it's not necessary, let's implement the ability to receive props in kebab-case, just like in the original Vue.\\\nAt this point, create a directory called `~/packages/shared` and create a file called `general.ts` in it.\\\nThis is the place to define general functions, not only for `runtime-core` and `runtime-dom`.\\\nFollowing the original Vue, let's implement `hasOwn` and `camelize`.\n\n`~/packages/shared/general.ts`\n\n```ts\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nexport const hasOwn = (\n  val: object,\n  key: string | symbol,\n): key is keyof typeof val => hasOwnProperty.call(val, key)\n\nconst camelizeRE = /-(\\w)/g\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))\n}\n```\n\nLet's use `camelize` in `componentProps.ts`.\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  // -------------------------------------------------------------- here\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value\n  })\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      // -------------------------------------------------------------- here\n      // kebab -> camel\n      let camelKey\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value\n      }\n    }\n  }\n}\n```\n\nNow you should be able to handle kebab-case as well. Let's check it in the playground.\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: { someMessage: string }) {\n    return () => h('div', {}, [`someMessage: ${props.someMessage}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { 'some-message': state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```"
  },
  {
    "path": "book/online-book/src/10-minimum-example/052-component-emits.md",
    "content": "# Component Emits\n\n## Developer Interface\n\nContinuing from props, let's implement emits.\\\nThe implementation of emits is relatively simple, so it will be finished quickly.\n\nIn terms of the developer interface, emits will be received from the second argument of the setup function.\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n## Implementation\n\nSimilar to props, let's create a file called `~/packages/runtime-core/componentEmits.ts` and implement it there.\\\n`~/packages/runtime-core/componentEmits.ts`\n\n```ts\nexport function emit(\n  instance: ComponentInternalInstance,\n  event: string,\n  ...rawArgs: any[]\n) {\n  const props = instance.vnode.props || {}\n  let args = rawArgs\n\n  let handler =\n    props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]\n\n  if (handler) handler(...args)\n}\n```\n\n`~/packages/shared/general.ts`\n\n```ts\nexport const capitalize = (str: string) =>\n  str.charAt(0).toUpperCase() + str.slice(1)\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``)\n```\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  emit: (event: string, ...args: any[]) => void\n}\n\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    emit: null!, // to be set immediately\n  }\n\n  instance.emit = emit.bind(null, instance)\n  return instance\n}\n```\n\nYou can pass this to the setup function.\n\n`~/packages/runtime-core/componentOptions.ts`\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function // To receive ctx.emit\n  render?: Function\n}\n```\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      // Pass emit\n      instance.render = component.setup(instance.props, {\n        emit: instance.emit,\n      }) as InternalRenderFunction;\n    }\n```\n\nLet's test the functionality with an example of the developer interface we assumed earlier!  \nIf it works properly, you can now communicate between components using props/emit!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system3)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/060-template-compiler.md",
    "content": "# Understanding the Template Compiler\n\n<KawaikoNote variant=\"question\" title=\"What is a compiler?\">\n\nA \"compiler\" is a translator. It converts human-friendly formats (template)\nto machine-friendly formats (h functions)!\n\n</KawaikoNote>\n\n## Actually, we have everything we need for the operation so far (?)\n\nSo far, we have implemented the Reactivity System, Virtual DOM, and Component.\nAlthough these are very small and not practical, it is not an exaggeration to say that we have understood the overall configuration elements necessary for operation.\nAlthough the functionality of each element itself is insufficient, it feels like we have gone through it superficially.\n\nFrom this chapter, we will implement the template functionality to make it closer to Vue.js. However, these are only for improving DX and do not affect the runtime.(Strictly speaking, compiler optimization may have an impact, but since it is not the main point, we will assume it does not.)\\\nTo be more specific, we will extend the developer interface for improving DX and \"eventually convert it to the internal implementation we have made so far\".\n\n## Developer interface we want to achieve this time\n\nAt the moment, the developer interface looks like this.\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\nCurrently, the View part is constructed using the h function. We want to be able to write the template in the template option to make it closer to raw HTML.\\\nHowever, it is difficult to implement various things all at once, so let's start with a limited set of features. \\\nFor now, let's divide it into the following tasks:\n\n1. Be able to render simple tags, messages, and static attributes.\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n2. Be able to render more complex HTML.\n\n```ts\nconst app = createApp({\n  template: `\n    <div>\n      <p>hello</p>\n      <button> click me! </button>\n    </div>\n  `,\n})\n```\n\n3. Be able to use what is defined in the setup function.\n\n```ts\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n\n    return { count, increment }\n  },\n\n  template: `\n    <div>\n      <p>count: {{ count }}</p>\n      <button v-on:click=\"increment\"> click me! </button>\n    </div>\n  `,\n})\n```\n\nWe will further divide each of them into smaller parts, but let's roughly divide them into these three steps.\nLet's start with step 1.\n\n## What the Compiler Does\n\nNow, the developer interface we are aiming for looks like this.\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\nFirst of all, let's talk about what a compiler is.\nWhen writing software, you will soon hear the word \"compiler\".\n\"Compile\" means translation, and in the field of software, it is often used to mean translating from higher-level descriptions to lower-level descriptions.\\\nDo you remember this word from the beginning of this book?\n\n> For convenience, we will call the closer to raw JS \"low-level developer interface\".\n> And, it is important to note that \"when starting implementation, start from the low-level part\".\n> The reason for this is that in many cases, high-level descriptions are converted to low-level descriptions and run.\n> In other words, 1 and 2 are ultimately converted to the form of 3 internally.\n> The implementation of this conversion is called a \"compiler\".\n\nSo, why do we need this thing called a compiler? One of the major purposes is to \"improve the development experience\".\nAt the very least, if a low-level interface that works is provided, it is possible to develop using only those functions.\nHowever, it can be cumbersome and troublesome to consider various parts that are not related to the functionality, and the description may be difficult to understand. Therefore, we will redevelop only the interface part with the user's feelings in mind.\n\nIn this regard, what Vue.js aims for is to \"write like raw HTML and use Vue's provided features (directives, etc.) to write views conveniently\".\nAnd, the ultimate goal is SFC.\\\nRecently, with the popularity of jsx/tsx, Vue also provides these as options for the developer interface. However, this time, let's try to implement Vue's original template.\n\nI have explained it in a long article, but in the end, what I want to do this time is to implement the ability to translate (compile) code like this:\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\ninto this:\n\n```ts\nconst app = createApp({\n  render() {\n    return h('p', { class: 'hello' }, ['Hello World'])\n  },\n})\n```\n\nTo narrow down the scope a little more, it is this part:\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\n<KawaikoNote variant=\"funny\" title=\"The true nature of templates\">\n\nTemplates are ultimately converted to h function calls.\nIn other words, the h function we implemented earlier will also be used here!\n\n</KawaikoNote>\n\nLet's implement it step by step in several phases.\n\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/061-template-compiler-impl.md",
    "content": "# Implementing a Template Compiler\n\n## Implementation Approach\n\nThe basic approach is to manipulate the string passed through the template option to generate specific functions. \\\nLet's divide the compiler into three elements.\n\n### Parsing\n\nParsing involves extracting necessary information from the given string. You can think of it like this:\n\n```ts\nconst { tag, props, textContent } = parse(`<p class=\"hello\">Hello World</p>`)\nconsole.log(tag) // \"p\"\nconsole.log(prop) // { class: \"hello\" }\nconsole.log(textContent) // \"Hello World\"\n```\n\n### Code Generation\n\nCode generation generates code (strings) based on the result of parsing.\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"h('p', { class: 'hello' }, ['Hello World']);\"\n```\n\n### Function Object Generation\n\nFunction object generation creates executable functions based on the code (strings) generated by codegen. \\\nIn JavaScript, you can generate functions from strings using the Function constructor.\n\n```ts\nconst f = new Function('return 1')\nconsole.log(f()) // 1\n\n// If you want to define arguments, you can do it like this\nconst add = new Function('a', 'b', 'return a + b')\nconsole.log(add(1, 1)) // 2\n```\n\nWe will use this to generate functions. \\\nOne thing to note here is that the generated function can only handle variables defined within it, so we need to include the import of functions like the h function in it.\n\n```ts\nimport * as runtimeDom from './runtime-dom'\nconst render = new Function('ChibiVue', code)(runtimeDom)\n```\n\nBy doing this, we can receive runtimeDom as ChibiVue and include the h function in the codegen stage as follows:\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"return () => { const { h } = ChibiVue; return h('p', { class: 'hello' }, ['Hello World']); }\"\n```\n\nIn other words, earlier we said that we would convert it like this:\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\nBut to be precise, we convert it like this:\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n\n// ↓\n\nChibiVue => {\n  return () => {\n    const { h } = ChibiVue\n    return h('p', { class: 'hello' }, ['Hello World'])\n  }\n}\n```\n\nAnd pass runtimeDom to generate the render function. \\\nThe responsibility of codegen is to generate the following string:\n\n```ts\nconst code = `\n  return () => {\n      const { h } = ChibiVue;\n      return h(\"p\", { class: \"hello\" }, [\"Hello World\"]);\n  };\n`\n```\n\n## Implementation\n\nOnce you understand the approach, let's implement it. \\\nCreate a directory called `compiler-core` in `~/packages` and create `index.ts`, `parse.ts`, and `codegen.ts` in it.\n\n```sh\npwd # ~/\nmkdir packages/compiler-core\ntouch packages/compiler-core/index.ts\ntouch packages/compiler-core/parse.ts\ntouch packages/compiler-core/codegen.ts\n```\n\nindex.ts is only used for exporting as usual.\n\nNow let's start implementing from parse.\n`packages/compiler-core/parse.ts`\n\n```ts\nexport const baseParse = (\n  content: string,\n): { tag: string; props: Record<string, string>; textContent: string } => {\n  const matched = content.match(/<(\\w+)\\s+([^>]*)>([^<]*)<\\/\\1>/)\n  if (!matched) return { tag: '', props: {}, textContent: '' }\n\n  const [_, tag, attrs, textContent] = matched\n\n  const props: Record<string, string> = {}\n  attrs.replace(/(\\w+)=[\"']([^\"']*)[\"']/g, (_, key: string, value: string) => {\n    props[key] = value\n    return ''\n  })\n\n  return { tag, props, textContent }\n}\n```\n\nAlthough it is a very simple parser using regular expressions, it is sufficient for the first implementation.\n\nNext, let's generate the code. Implement it in codegen.ts.\\\n`packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = ({\n  tag,\n  props,\n  textContent,\n}: {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}): string => {\n  return `return () => {\n  const { h } = ChibiVue;\n  return h(\"${tag}\", { ${Object.entries(props)\n    .map(([k, v]) => `${k}: \"${v}\"`)\n    .join(', ')} }, [\"${textContent}\"]);\n}`\n}\n```\n\nNow, let's implement a function that generates a function string from a template by combining these.\\\n Create a new file called `packages/compiler-core/compile.ts`.\n\n`packages/compiler-core/compile.ts`\n\n```ts\nimport { generate } from './codegen'\nimport { baseParse } from './parse'\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template)\n  const code = generate(parseResult)\n  return code\n}\n```\n\nThis shouldn't be too difficult. In fact, the responsibility of `compiler-core` ends here.\n\n## Runtime Compiler and Build Process Compiler\n\nIn fact, Vue has two types of compilers. \\\nOne is the compiler that runs on the runtime (in the browser), and the other is the compiler that runs in the build process (such as Node.js). \\\nSpecifically, the runtime compiler is responsible for compiling the template option or the template provided as HTML, while the build process compiler is responsible for compiling SFC (or JSX). \\\nThe template option we are currently implementing falls into the former category.\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\nThe template provided as HTML is a developer interface where you write Vue templates in HTML. \\\n(It is convenient for quickly incorporating it into HTML via CDN, etc.)\n\n```ts\nconst app = createApp()\napp.mount('#app')\n```\n\n```html\n<div id=\"app\">\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert('hello')\">click me!</button>\n</div>\n```\n\nBoth of these need to be compiled, but the compilation is performed in the browser.\n\nOn the other hand, SFC compilation is performed during the project build, and only the compiled code exists on the runtime. \\\n(You need to set up a bundler such as Vite or webpack in your development environment.)\n\n```vue\n<!-- App.vue -->\n<script>\nexport default {}\n</script>\n\n<template>\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert(\"hello\")\">click me!</button>\n</template>\n```\n\n```ts\nimport App from 'App.vue'\nconst app = createApp(App)\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\nThe important point to note is that both compilers share common processing. \\\nThe source code for this common part is implemented in the `compiler-core` directory. \\\nAnd the runtime compiler and SFC compiler are implemented in the `compiler-dom` and `compiler-sfc` directories, respectively. \\\nPlease take a look at this diagram again.\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n## Continued Implementation\n\nWe've jumped ahead a bit, but let's continue with the implementation. \\\nAlthough I would like to implement `packages/index.ts`, there is some preparation work to be done, so let's do that first. \\\nThe preparation work is to implement a variable in `packages/runtime-core/component.ts` to hold the compiler itself, and a registration function.\n\n`packages/runtime-core/component.ts`\n\n```ts\ntype CompileFunction = (template: string) => InternalRenderFunction\nlet compile: CompileFunction | undefined\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile\n}\n```\n\nNow, let's generate the function in `packages/index.ts` and register it.\n\n```ts\nimport { compile } from './compiler-dom'\nimport { InternalRenderFunction, registerRuntimeCompiler } from './runtime-core'\nimport * as runtimeDom from './runtime-dom'\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template)\n  return new Function('ChibiVue', code)(runtimeDom)\n}\n\nregisterRuntimeCompiler(compileToFunction)\n\nexport * from './runtime-core'\nexport * from './runtime-dom'\nexport * from './reactivity'\n```\n\n※ Don't forget to export the `h` function from `runtime-dom` because it needs to be included in `runtimeDom`.\n\n```ts\nexport { h } from '../runtime-core'\n```\n\nNow that the compiler is registered, let's actually perform the compilation. \\\nSince the template is required in the component options type, let's add the template for now.\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function\n  render?: Function\n  template?: string // Added\n}\n```\n\nNow, let's compile the important part.\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  // ----------------------- From here\n  const { props } = instance.vnode\n  initProps(instance, props)\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n  // ----------------------- To here\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\nWe will extract the above part in `packages/runtime-core/component.ts`.\n\n`packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n}\n```\n\n`packages/runtime-core/renderer.ts`\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // prettier-ignore\n  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n  setupComponent(instance)\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\nNow, let's perform the compilation inside the `setupComponent` function.\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n\n  // ------------------------ Here\n  if (compile && !component.render) {\n    const template = component.template ?? ''\n    if (template) {\n      instance.render = compile(template)\n    }\n  }\n}\n```\n\nNow, we should be able to compile simple HTML using the `template` option. \\\nLet's try it in the playground!\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n![Simple template compiler output before cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-before.png)\n\nIt seems to be working fine. \\\nLet's try making some changes to see if they are reflected.\n\n```ts\nconst app = createApp({\n  template: `<b class=\"hello\" style=\"color: red;\">Hello World!!</b>`,\n})\napp.mount('#app')\n```\n\n![Simple template compiler output after cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-after.png)\n\nIt appears to be implemented correctly!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/070-more-complex-parser.md",
    "content": "# I want to write more complex HTML\n\n## I want to write more complex HTML\n\nIn the current state, I can only express the names and attributes of tags, and the content of text.\\\nTherefore, I want to be able to write more complex HTML in the template.\\\nSpecifically, I want to be able to compile a template like this:\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n\n  `,\n})\napp.mount('#app')\n```\n\nHowever, it is difficult to parse such complex HTML with regular expressions. \\\nSo, from here, I will implement a parser in earnest.\n\n## Introduction of AST\n\nIn order to implement a full-fledged compiler, I will introduce something called AST (Abstract Syntax Tree).\\\nAST stands for Abstract Syntax Tree, and as the name suggests, it is a data representation of a tree structure that represents syntax.\\\nThis is a concept that appears when implementing various compilers, not just Vue.js.\\\nIn many cases (in language processing systems), \"parsing\" refers to converting it into this representation called AST.\\\nThe definition of AST is defined by each language.\\\nFor example, JavaScript, which you are familiar with, is represented by AST called [estree](https://github.com/estree/estree), and the source code string is parsed according to this definition.\n\nI tried to explain it in a cool way, but in terms of image, it is just a formal definition of the return type of the parse function that we have implemented so far.\\\nCurrently, the return value of the parse function is as follows:\n\n```ts\ntype ParseResult = {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}\n```\n\nLet's extend this and define it so that more complex expressions can be performed.\n\nCreate a new file `~/packages/compiler-core/ast.ts`.\\\nI will explain while writing the code because it is a bit long.\n\n```ts\n// This represents the type of node.\n// It should be noted that the Node here does not refer to the HTML Node, but rather the granularity handled by this template compiler.\n// So, not only Element and Text, but also Attribute are treated as one Node.\n// This is in line with the design of Vue.js and will be useful when implementing directives in the future.\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  ATTRIBUTE,\n}\n\n// All Nodes have type and loc.\n// loc stands for location and holds information about where this Node corresponds to in the source code (template string).\n// (e.g. which line and where on the line)\nexport interface Node {\n  type: NodeTypes\n  loc: SourceLocation\n}\n\n// Node for Element.\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string // e.g. \"div\"\n  props: Array<AttributeNode> // e.g. { name: \"class\", value: { content: \"container\" } }\n  children: TemplateChildNode[]\n  isSelfClosing: boolean // e.g. <img /> -> true\n}\n\n// Attribute that ElementNode has.\n// It could have been expressed as just Record<string, string>,\n// but it is defined to have name(string) and value(TextNode) like Vue.\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE\n  name: string\n  value: TextNode | undefined\n}\n\nexport type TemplateChildNode = ElementNode | TextNode\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT\n  content: string\n}\n\n// Information about location.\n// Node has this information.\n// start and end contain position information.\n// source contains the actual code (string).\nexport interface SourceLocation {\n  start: Position\n  end: Position\n  source: string\n}\n\nexport interface Position {\n  offset: number // from start of file\n  line: number\n  column: number\n}\n```\n\nThis is the AST we will be dealing with this time.\\\nIn the parse function, we will implement the conversion of the template string into this AST.\n\n## Implementation of a full-fledged parser\n\n::: warning\nIn late November 2023, a major rewrite for performance improvement was conducted in [vuejs/core#9674](https://github.com/vuejs/core/pull/9674).  \nThese changes were released as [Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) in late December 2023.  \nPlease note that this online book refers to the implementation prior to this rewrite.  \nWe plan to update this online book accordingly at the appropriate timing.\n:::\n\nImplement it in `~/packages/compiler-core/parse.ts`.\nEven if I say it's full-fledged, you don't have to be too nervous.\\\nBasically, all you're doing is generating an AST while reading the string and using branching and looping.\\\nThe source code will be a bit longer, but I think the explanation will be easier to understand in the code base. So let's proceed that way.\\\nPlease try to understand the details by reading the source code.\n\nDelete the contents of baseParse that you have implemented so far, and change the return type as follows:\n\n```ts\nimport { TemplateChildNode } from './ast'\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  // TODO:\n  return { children: [] }\n}\n```\n\n## Context\n\nFirst, let's implement the state used during parsing.\n\\We will name it `ParserContext` and gather the necessary information during parsing here.\\\nEventually, I think it will also hold parser configuration options, etc.\n\n```ts\nexport interface ParserContext {\n  // The original template string\n  readonly originalSource: string\n\n  source: string\n\n  // The current position that this parser is reading\n  offset: number\n  line: number\n  column: number\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  }\n}\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content) // Create context\n\n  // TODO:\n  return { children: [] }\n}\n```\n\n## parseChildren\n\nIn terms of order, the parsing progresses as follows: (parseChildren) -> (parseElement or parseText).\n\nAlthough it is a bit long, let's start with the implementation of parseChildren.\\\nThe explanation will be done in the comments in the source code.\n\n```ts\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content)\n  const children = parseChildren(context, []) // Parse child nodes\n  return { children: children }\n}\n\nfunction parseChildren(\n  context: ParserContext,\n\n  // Since HTML has a recursive structure, we keep the ancestor elements as a stack and push them each time we nest in a child.\n  // When an end tag is found, parseChildren ends and pops the ancestors.\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (s[0] === '<') {\n      // If s starts with \"<\" and the next character is an alphabet, it is parsed as an element.\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors) // TODO: Implement this later.\n      }\n    }\n\n    if (!node) {\n      // If it does not match the above conditions, it is parsed as a TextNode.\n      node = parseText(context) // TODO: Implement this later.\n    }\n\n    pushNode(nodes, node)\n  }\n\n  return nodes\n}\n\n// Function to determine the end of the while loop for parsing child elements\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // If s starts with \"</\" and the tag name of ancestors follows, it determines whether there is a closing tag (whether parseChildren should end).\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString)\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  // If nodes of type Text are continuous, they are combined.\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes)\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content\n      return\n    }\n  }\n\n  nodes.push(node)\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1]\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, '</') &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || '>')\n  )\n}\n```\n\nNext, let's implement parseElement and parseText.\n\n::: tip About the isEnd Loop\nIn isEnd, there is a loop process that checks whether 's' starts with the closing tag of each element in the ancestors array using startsWithEndTagOpen.\n\n```ts\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // If s starts with </ and the tag name of ancestors follows, it determines whether there is a closing tag (whether parseChildren should end).\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n```\n\nHowever, if you need to check whether 's' starts with a closing tag, it should be sufficient to check only the last element in ancestors. \\\nAlthough this section of code was eliminated in a recent rewrite of the parser, modifying the Vue 3.3 code to only check the last element in ancestors still results in all the positive tests passing successfully.\n:::\n\n## parseText\n\nFirst, let's start with the simple parseText.\\\nIt is a bit long because it also implements some utilities that are used not only in parseText but also in other functions.\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  // Read until \"<\" (regardless of whether it is the start or end tag), and calculate the index of the end point of the Text data based on how many characters were read.\n  const endToken = '<'\n  let endIndex = context.source.length\n  const index = context.source.indexOf(endToken, 1)\n  if (index !== -1 && endIndex > index) {\n    endIndex = index\n  }\n\n  const start = getCursor(context) // For loc\n\n  // Parse Text data based on the information of endIndex.\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n\n// Extract text based on content and length.\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length)\n  advanceBy(context, length)\n  return rawText\n}\n\n// -------------------- The following are utilities (also used in parseElement, etc.) --------------------\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context\n  advancePositionWithMutation(context, source, numberOfCharacters)\n  context.source = source.slice(numberOfCharacters)\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\n// Although it is a bit long, it simply calculates the position.\n// It destructively updates the pos object received as an argument.\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0\n  let lastNewLinePos = -1\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++\n      lastNewLinePos = i\n    }\n  }\n\n  pos.offset += numberOfCharacters\n  pos.line += linesCount\n  pos.column =\n    lastNewLinePos === -1\n      ? pos.column + numberOfCharacters\n      : numberOfCharacters - lastNewLinePos\n\n  return pos\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context\n  return { column, line, offset }\n}\n\nfunction getSelection(\n  context: ParserContext,\n  start: Position,\n  end?: Position,\n): SourceLocation {\n  end = end || getCursor(context)\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  }\n}\n```\n\n## parseElement\n\nNext is the parsing of elements.  \nThe parsing of elements mainly consists of parsing the start tag, parsing child nodes, and parsing the end tag.\\\nThe parsing of the start tag is further divided into tag name and attributes.\\\nLet's start by creating a framework for parsing the first half of the start tag, child nodes, and the end tag.\n\n```ts\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start) // TODO:\n\n  // If it is a self-closing element like <img />, we end here (since there are no children or end tag).\n  if (element.isSelfClosing) {\n    return element\n  }\n\n  // Children.\n  ancestors.push(element)\n  const children = parseChildren(context, ancestors)\n  ancestors.pop()\n\n  element.children = children\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End) // TODO:\n  }\n\n  return element\n}\n```\n\nThere is nothing particularly difficult here.\\\nThe `parseChildren` function is recursive (since `parseElement` is called by `parseChildren`).\\\nWe are manipulating the `ancestors` data structure as a stack before and after.\n\nLet's implement `parseTag`.\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context)\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!\n  const tag = match[1]\n\n  advanceBy(context, match[0].length)\n  advanceSpaces(context)\n\n  // Attributes.\n  let props = parseAttributes(context, type)\n\n  // Tag close.\n  let isSelfClosing = false\n\n  // If the next characters are \"/>\", it is a self-closing tag.\n  isSelfClosing = startsWith(context.source, '/>')\n  advanceBy(context, isSelfClosing ? 2 : 1)\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  }\n}\n\n// Parsing the entire attributes (multiple attributes).\n// eg. `id=\"app\" class=\"container\" style=\"color: red\"`\nfunction parseAttributes(\n  context: ParserContext,\n  type: TagType,\n): AttributeNode[] {\n  const props = []\n  const attributeNames = new Set<string>()\n\n  // Continue reading until the end of the tag.\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, '>') &&\n    !startsWith(context.source, '/>')\n  ) {\n    const attr = parseAttribute(context, attributeNames)\n\n    if (type === TagType.Start) {\n      props.push(attr)\n    }\n\n    advanceSpaces(context) // Skip spaces.\n  }\n\n  return props\n}\n\ntype AttributeValue =\n  | {\n      content: string\n      loc: SourceLocation\n    }\n  | undefined\n\n// Parsing a single attribute.\n// eg. `id=\"app\"`\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode {\n  // Name.\n  const start = getCursor(context)\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!\n  const name = match[0]\n\n  nameSet.add(name)\n\n  advanceBy(context, name.length)\n\n  // Value\n  let value: AttributeValue = undefined\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context)\n    advanceBy(context, 1)\n    advanceSpaces(context)\n    value = parseAttributeValue(context)\n  }\n\n  const loc = getSelection(context, start)\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  }\n}\n\n// Parsing the value of an attribute.\n// This implementation allows values to be parsed whether they are single-quoted or double-quoted.\n// It simply extracts the value enclosed in quotes.\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context)\n  let content: string\n\n  const quote = context.source[0]\n  const isQuoted = quote === `\"` || quote === `'`\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1)\n\n    const endIndex = context.source.indexOf(quote)\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length)\n    } else {\n      content = parseTextData(context, endIndex)\n      advanceBy(context, 1)\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source)\n    if (!match) {\n      return undefined\n    }\n    content = parseTextData(context, match[0].length)\n  }\n\n  return { content, loc: getSelection(context, start) }\n}\n```\n\n## After finishing the implementation of the parser\n\nI have written a lot of code, more than usual. (It's only about 300 lines at most)\\\nI think it would be better to read the implementation here rather than explaining it in special words, so please read it repeatedly.\\\nAlthough I have written a lot, basically it is a straightforward task of advancing the analysis by reading the string, and there are no particularly difficult techniques.\\\n\nBy now, you should be able to generate an AST. Let's check if the parsing is working.\\\nHowever, since the codegen part has not been implemented yet, we will output it to the console for confirmation this time.\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\napp.mount('#app')\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim()) // Trim the template\n  console.log(\n    '🚀 ~ file: compile.ts:6 ~ baseCompile ~ parseResult:',\n    parseResult,\n  )\n\n  // TODO: codegen\n  // const code = generate(parseResult);\n  // return code;\n  return ''\n}\n```\n\nThe screen will not display anything, but let's check the console.\n\n![AST output for complex HTML](/figures/10-minimum-example/more-complex-parser/complex-html-ast.png)\n\nIt seems that the parsing is going well.\\\nNow, let's proceed with the implementation of the codegen based on the generated AST.\n\n## Generating the render function based on the AST\n\nNow that we have implemented a full-fledged parser, let's create a code generator that can be applied to it.\\\nHowever, at this point, there is no need for a complex implementation.\\\nI will show you the code first.\n\n```ts\nimport { ElementNode, NodeTypes, TemplateChildNode, TextNode } from './ast'\n\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render() {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node)\n    case NodeTypes.TEXT:\n      return genText(node)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) => `${name}: \"${value?.content}\"`)\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``\n}\n```\n\nWith the above code, you can create something that works.\\\nUncomment the part that was commented out in the parser chapter and check the actual operation.\\\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult)\n  return code\n}\n```\n\nplayground\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\n![Rendered template result in the browser](/figures/10-minimum-example/more-complex-parser/render-template-result.png)\n\nHow about that? It seems that we can render the screen very nicely.\n\nLet's add some movement to the screen. \\\nSince we haven't implemented template binding, we will directly manipulate the DOM.\n\n```ts\nexport type ComponentOptions = {\n  // .\n  // .\n  // .\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | void // Allow void as well\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // Delay the processing with Promise.resolve so that DOM operations can be performed after mounting\n    Promise.resolve().then(() => {\n      const btn = document.getElementById('btn')\n      btn &&\n        btn.addEventListener('click', () => {\n          const h2 = document.getElementById('hello')\n          h2 && (h2.textContent += '!')\n        })\n    })\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2 id=\"hello\">Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button id=\"btn\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\nLet's make sure it is working correctly.\\\nHow about that? Although the functionality is limited, it is getting closer to the usual Vue developer interface.\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler2)"
  },
  {
    "path": "book/online-book/src/10-minimum-example/080-template-binding.md",
    "content": "# Data Binding\n\n## Want to bind to the template\n\nCurrently, we are directly manipulating the DOM, so we are not able to take advantage of the Reactivity System or Virtual DOM.  \nIn reality, we want to write event handlers and text content in the template section. That's where the joy of declarative UI comes in.  \nWe aim for a developer interface like the following.\n\n```ts\nimport { createApp, reactive, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render() {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', {}, `message: ${this.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', {}, [h('b', {}, 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onclick: this.changeMessage }, 'click me!'),\n      h(\n        'style',\n        {},\n        `\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      `,\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nNow, I want to be able to handle the values returned from the `setup` function in the template. \\\nFrom now on, I will refer to this as \"template binding\" or simply \"binding\". \\\nI am going to implement the binding, but before implementing event handlers and mustache syntax, there are a few things I want to do.\n\nI mentioned the value returned from `setup`, but currently the return value of `setup` is either `undefined` or a function (render function).\\\nAs a preparation for implementing binding, I need to modify it so that `setup` can return state and other values, and these values can be stored as component data.\n\n```ts\nexport type ComponentOptions = {\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void\n  // Allow returning Record<string, unknown>\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupState: Data // Store the result of setup as an object here\n}\n```\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n\n    // Branch based on the type of setupResult\n    if (typeof setupResult === 'function') {\n      instance.render = setupResult\n    } else if (typeof setupResult === 'object' && setupResult !== null) {\n      instance.setupState = setupResult\n    } else {\n      // do nothing\n    }\n  }\n  // .\n  // .\n  // .\n}\n```\n\nFrom now on, I will refer to the data defined in `setup` as `setupState`.\n\nNow, before implementing the compiler, let's think about how to bind `setupState` to the template. \\\nPreviously, we bound `setupState` like this:\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return () => h('div', {}, [state.message])\n  },\n})\n```\n\nWell, it's not really binding, but rather the render function simply forms a closure and references the variable. \\\nHowever, this time, since the setup option and the render function are conceptually different, we need to find a way to pass the setup data to the render function.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  // This will be converted to a render function\n  template: '<div>{{ state.message }}</div>',\n})\n```\n\nThe `template` is compiled as a render function using the `h` function and assigned to `instance.render`. \\\nSo, it is equivalent to the following code:\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render() {\n    return h('div', {}, [state.message])\n  },\n})\n```\n\nNaturally, the variable `state` is not defined within the render function.\\\nNow, how can we reference the `state` variable?\n\n## Using the `with` statement\n\nIn conclusion, we can use the `with` statement to achieve the desired result:\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render(ctx) {\n    with (ctx) {\n      return h('div', {}, [state.message])\n    }\n  },\n})\n```\n\nI believe that there are many people who are not familiar with the `with` statement.\n\nAnd for good reason, this feature is deprecated.\n\nAccording to MDN:\n\n> Although still supported by some browsers, it has been deprecated from the Web standards. However, it may still be in use for various purposes, such as compatibility with legacy code. Avoid using it, and update existing code if possible.\n\nTherefore, it is recommended to avoid using it.\n\nWe do not know how the implementation of Vue.js will change in the future, but since Vue.js 3 uses the `with` statement, we will use it for this implementation.\n\nA little side note, not everything in Vue.js is implemented using the `with` statement.\\\nWhen dealing with templates in Single File Components (SFC), it is implemented without using the `with` statement. \\\nWe will cover this in a later chapter, but for now, let's consider implementing it using `with`.\n\n---\n\nNow, let's review the behavior of the `with` statement.\nThe `with` statement extends the scope chain for a statement.\n\nIt behaves as follows:\n\n```ts\nconst obj = { a: 1, b: 2 }\n\nwith (obj) {\n  console.log(a, b) // 1, 2\n}\n```\n\nBy passing the parent object that contains the `state` as an argument to `with`, we can reference the `state` variable.\n\nIn this case, we will treat `setupState` as the parent object.\\\nIn reality, not only `setupState`, but also data from `props` and data defined in Options API should be accessible.\\\n However, for now, we will only consider using the data from `setupState`.\n(We will cover the implementation of this part in a later section, as it is not part of the minimal implementation.)\n\nTo summarize what we want to achieve this time, we want to compile the following template:\n\n```html\n<div>\n  <p>{{ state.message }}</p>\n  <button @click=\"changeMessage\">click me</button>\n</div>\n```\n\ninto the following function:\n\n```ts\n_ctx => {\n  with (_ctx) {\n    return h('div', {}, [\n      h('p', {}, [state.message]),\n      h('button', { onClick: changeMessage }, ['click me']),\n    ])\n  }\n}\n```\n\nAnd pass `setupState` to this function:\n\n```ts\nconst setupState = setup()\nrender(setupState)\n```\n\n## Implementing the Mustache Syntax\n\nFirst, let's implement the Mustache syntax.\\\nAs usual, we will consider the AST, implement the parser, and then implement the code generator.\\\nCurrently, the only nodes defined as part of the AST are `Element`, `Text`, and `Attribute`.\\\nSince we want to define the Mustache syntax, it intuitively makes sense to have an AST called `Mustache`.\\\nFor that purpose, we will use the `Interpolation` node.\\\nInterpolation has meanings such as \"interpolation\" or \"insertion\".\\\nTherefore, the AST we will handle this time will look like this:\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION, // Added\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode // Added InterpolationNode\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // The content written inside the Mustache (in this case, the single variable name defined in setup will be placed here)\n}\n```\n\nNow that the AST has been implemented, let's move on to implementing the parser.\\\nWhen we find the string <span v-pre>`{{`</span>, we will parse it as an `Interpolation`.\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[]\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n\n    if (startsWith(s, \"{{\")) { // Here\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n    // .\n    // .\n    //\n    }\n```\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  const [open, close] = ['{{', '}}']\n  const closeIndex = context.source.indexOf(close, open.length)\n  if (closeIndex === -1) return undefined\n\n  const start = getCursor(context)\n  advanceBy(context, open.length)\n\n  const innerStart = getCursor(context)\n  const innerEnd = getCursor(context)\n  const rawContentLength = closeIndex - open.length\n  const rawContent = context.source.slice(0, rawContentLength)\n  const preTrimContent = parseTextData(context, rawContentLength)\n\n  const content = preTrimContent.trim()\n\n  const startOffset = preTrimContent.indexOf(content)\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset)\n  }\n  const endOffset =\n    rawContentLength - (preTrimContent.length - content.length - startOffset)\n  advancePositionWithMutation(innerEnd, rawContent, endOffset)\n  advanceBy(context, close.length)\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\nThere are cases where <span v-pre>`{{`</span> appears in the text, so we will make some modifications to `parseText`.\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = ['<', '{{'] // If <span v-pre>`{{`</span> appears, parseText ends\n\n  let endIndex = context.source.length\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1)\n    if (index !== -1 && endIndex > index) {\n      endIndex = index\n    }\n  }\n\n  const start = getCursor(context)\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\nFor those who have implemented the parser so far, there should be no particularly difficult parts. \\\nIt simply searches for <span v-pre>`{{`</span> and reads until <span v-pre>`}}`</span> comes, generating an AST.  \\\nIf <span v-pre>`}}`</span> is not found, it returns undefined and parses it as text in the branching of parseText.\n\nLet's output to the console or something to make sure that the parsing is working properly.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Interpolation AST output](/figures/10-minimum-example/template-binding/parse-interpolation-ast.png)\n\nIt looks fine!\n\nNow let's implement the binding based on this AST.  \\\nWrap the contents of the render function with a with statement.\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    // .\n    // .\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node)\n    // .\n    // .\n  }\n}\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`\n}\n```\n\nFinally, when executing the render function, pass `setupState` as an argument.\\\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild // Accept ctx as an argument\n}\n```\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render, setupState } = instance\n    if (!instance.isMounted) {\n      // .\n      // .\n      // .\n      const subTree = (instance.subTree = normalizeVNode(render(setupState))) // Pass setupState\n      // .\n      // .\n      // .\n    } else {\n      // .\n      // .\n      // .\n      const nextTree = normalizeVNode(render(setupState)) // Pass setupState\n      // .\n      // .\n      // .\n    }\n  }\n}\n```\n\nIf you have come this far, you should be able to render. Let's check it!\n\n![Rendered interpolation result in the browser](/figures/10-minimum-example/template-binding/render-interpolation-result.png)\n\nThis completes the first binding!\n\n## First Directive\n\nNext is the event handler.\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) =>\n      // Convert props name to onClick if it is @click\n      name === '@click'\n        ? `onClick: ${value?.content}`\n        : `${name}: \"${value?.content}\"`,\n    )\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n```\n\nLet's check the operation.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\nYou did it! Well done! It's complete!\n\nI want to say that, but the implementation is not clean enough, so I think I'll refactor it a bit.\\\nSince `@click` is classified under the name \"directive\", it would be easy to imagine implementing `v-bind` and `v-model` in the future.\\\nSo let's represent it as `DIRECTIVE` in the AST and distinguish it from simple `ATTRIBUTE`.\n\nAs usual, let's implement it in the order of AST -> parse -> codegen.\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE, // added\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode> // props is an array of AttributeNode and DirectiveNode union\n  // .\n  // .\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  // Represents the format of `v-name:arg=\"exp\"`.\n  // eg. For `v-on:click=\"increment\"`, it would be { name: \"on\", arg: \"click\", exp=\"increment\" }\n  name: string\n  arg: string\n  exp: string\n}\n```\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // --------------------------------------------------- From here\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match =\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name\n      )!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n  // --------------------------------------------------- To here\n  // .\n  // .\n  // .\n```\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop))\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n```\n\nNow, let's check the operation in the playground.\\\nYou should be able to handle not only `@click`, but also `v-on:click` and other events.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Compiled directive result in the browser](/figures/10-minimum-example/template-binding/compile-directives-result.png)\n\nYou did it.\\\nWe're getting closer to Vue!  \nWith this, the implementation of the small template is complete. Good job.\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler3)\n\n<!-- It seems to be working properly, so we have finished implementing the three tasks that were split when starting the compiler implementation. Well done! -->\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/090-prerequisite-knowledge-for-the-sfc.md",
    "content": "## Surrounding Knowledge\n\n<KawaikoNote variant=\"question\" title=\"What is SFC?\">\n\nSFC is a Vue-specific format that combines template, script, and style in one file.\nIt's saved as `.vue` files and converted to JavaScript by build tools!\n\n</KawaikoNote>\n\n## How is SFC implemented?\n\nNow, let's finally start working on supporting Single File Component (SFC).  \nSo, how should we go about supporting it? SFC, like templates, is used during development and does not exist in the runtime.  \nFor those of you who have finished developing the template, I think it's a simple matter of how to compile it.\n\nYou just need to convert the following SFC code:\n\n```vue\n<script>\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>message: {{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\ninto the following JS code:\n\n```ts\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render(_ctx) {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', `message: ${_ctx.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', [h('b', 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onClick: _ctx.changeMessage }, 'click me!'),\n    ])\n  },\n}\n```\n\nYou may be wondering about the styles! But for now, let's forget about that and focus on the template and script.\\\nWe will not cover `script setup` in the minimum example.\n\n## When and how should we compile?\n\nIn conclusion, \"we compile when the build tool resolves the dependencies\".\nIn most cases, SFC is imported and used from other files.\nAt this time, we write a plugin that compiles the `.vue` file when it is resolved and binds the result to the App.\n\n```ts\nimport App from './App.vue' // Compile when App.vue is imported\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\nThere are various build tools, but this time let's try writing a plugin for Vite.\n\nSince there may be few people who have never written a Vite plugin, let's start by getting familiar with plugin implementation using a simple sample code. Let's create a simple Vue project for now.\n\n```sh\npwd # ~\npnpm dlx create-vite\n## ✔ Project name: … plugin-sample\n## ✔ Select a framework: › Vue\n## ✔ Select a variant: › TypeScript\n\ncd plugin-sample\nni\n```\n\nLet's take a look at the vite.config.ts file of the created project.\n\n```ts\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue()],\n})\n```\n\nYou can see that it adds `@vitejs/plugin-vue` to the plugins.\nActually, when creating a Vue project with Vite, SFC can be used thanks to this plugin.\nThis plugin implements the SFC compiler according to the Vite plugin API, and compiles Vue files into JS files.\nLet's try creating a simple plugin in this project.\n\n```ts\nimport { defineConfig, Plugin } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue(), myPlugin()],\n})\n\nfunction myPlugin(): Plugin {\n  return {\n    name: 'vite:my-plugin',\n\n    transform(code, id) {\n      if (id.endsWith('.sample.js')) {\n        let result = ''\n\n        for (let i = 0; i < 100; i++) {\n          result += `console.log(\"HelloWorld from plugin! (${i})\");\\n`\n        }\n\n        result += code\n\n        return { code: result }\n      }\n    },\n  }\n}\n```\n\nI created it with the name `myPlugin`.\\\nSince it's simple, I think many of you can understand it without explanation, but I'll explain it just in case.\n\nThe plugin conforms to the format required by Vite. \\\nThere are various options, but since this is a simple sample, I only used the `transform` option.\\\nI recommend checking the official documentation and other resources for more information: https://vite.dev/guide/api-plugin\n\nIn the `transform` function, you can receive `code` and `id`. \\\nYou can think of `code` as the content of the file and `id` as the file name.\\\nAs a return value, you put the result in the `code` property.\nYou can write different processing for each file type based on the `id`, or modify the `code` to rewrite the content of the file.\\\nIn this case, I added 100 console logs to the beginning of the file's content for files ending with `*.sample.js`.\\\nNow, let's implement a sample `plugin.sample.js` and check it.\n\n```sh\npwd # ~/plugin-sample\ntouch src/plugin.sample.js\n```\n\n`~/plugin-sample/src/plugin.sample.js`\n\n```ts\nfunction fizzbuzz(n) {\n  for (let i = 1; i <= n; i++) {\n    i % 3 === 0 && i % 5 === 0\n      ? console.log('fizzbuzz')\n      : i % 3 === 0\n        ? console.log('fizz')\n        : i % 5 === 0\n          ? console.log('buzz')\n          : console.log(i)\n  }\n}\n\nfizzbuzz(Math.floor(Math.random() * 100) + 1)\n```\n\n`~/plugin-sample/src/main.ts`\n\n```ts\nimport { createApp } from 'vue'\nimport './style.css'\nimport App from './App.vue'\nimport './plugin.sample.js' // 追加\n\ncreateApp(App).mount('#app')\n```\n\nLet's check it in the browser.\n\n```sh\npwd # ~/plugin-sample\npnpm dev\n```\n\n![Sample Vite plugin console output](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-console.png)\n\n![Sample Vite plugin transformed source](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-source.png)\n\nYou can see that the source code has been modified properly.\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/091-parse-sfc.md",
    "content": "# Implementing the SFC Parser\n\n## Preparation\n\nAlthough this is a sample plugin that we created earlier, let's delete it because it is no longer needed.\n\n```sh\npwd # ~\nrm -rf ./plugin-sample\n```\n\nAlso, install the main Vite package in order to create a Vite plugin.\n\n```sh\npwd # ~\npnpm add vite\n```\n\nThis is the main part of the plugin, but since this is originally outside the scope of vuejs/core, we will create a directory called `@extensions` in the `packages` directory and implement it there.\n\n```sh\npwd # ~\nmkdir -p packages/@extensions/vite-plugin-chibivue\ntouch packages/@extensions/vite-plugin-chibivue/index.ts\n```\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      return { code }\n    },\n  }\n}\n```\n\nNow, let's implement the SFC compiler. \\\nHowever, it may be difficult to imagine without any substance, so let's implement a playground and do it while running it.  \nWe will create a simple SFC and load it.\n\n```sh\npwd # ~\ntouch examples/playground/src/App.vue\n```\n\n`examples/playground/src/App.vue`\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n`playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n`playground/vite.config.js`\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nimport chibivue from '../../packages/@extensions/vite-plugin-chibivue'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n  plugins: [chibivue()],\n})\n```\n\nLet's try starting in this state.\n\n![Vite error before the SFC plugin is implemented](/figures/10-minimum-example/parse-sfc/vite-error.png)\n\nOf course, it will result in an error. Well done (?).\n\n## Resolving the Error\n\nLet's resolve the error for now. We don't aim for perfection right away.\\\nFirst, let's limit the target of `transform` to \"\\*.vue\".\\\nWe can write a branching statement with `id` as we did in the sample, but since Vite provides a function called `createFilter`, let's create a filter using that.\\\n(There is no particular reason for this.)\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\nWe created a filter and transformed the file content to `export default {}` if it was a Vue file.\\\nThe error should disappear and the screen should not display anything.\n\n## Implementation of the Parser on compiler-sfc\n\nNow, this is just a temporary solution, so let's implement a proper solution.\\\nThe role of vite-plugin is to enable transformation with Vite, so the parsing and compilation are in the main Vue package.\\\nThat is the `compiler-sfc` directory.\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\nThe SFC compiler is the same for both Vite and Webpack. \\\nThe core implementation is in `compiler-sfc`.\n\nLet's create `compiler-sfc`.\n\n```sh\npwd # ~\nmkdir packages/compiler-sfc\ntouch packages/compiler-sfc/index.ts\n```\n\nIn SFC compilation, the SFC is represented by an object called `SFCDescriptor`.\n\n```sh\ntouch packages/compiler-sfc/parse.ts\n```\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { SourceLocation } from '../compiler-core'\n\nexport interface SFCDescriptor {\n  id: string\n  filename: string\n  source: string\n  template: SFCTemplateBlock | null\n  script: SFCScriptBlock | null\n  styles: SFCStyleBlock[]\n}\n\nexport interface SFCBlock {\n  type: string\n  content: string\n  loc: SourceLocation\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: 'template'\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: 'script'\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: 'style'\n}\n```\n\nWell, there's nothing particularly difficult. \\\nIt's just an object that represents the SFC information.\n\nIn `packages/compiler-sfc/parse.ts`, we will parse the SFC file (string) into `SFCDescriptor`.\\\nSome of you may be thinking, \"What? You worked so hard on the template parser, and now you're creating another parser...? It's a hassle.\" But don't worry.\\\nThe parser we're going to implement here is not a big deal. That's because we're just separating the template, script, and style by combining what we've created so far.\n\nFirst, as a preparation, export the template parser we created earlier.\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nimport { baseCompile, baseParse } from '../compiler-core'\n\nexport function compile(template: string) {\n  return baseCompile(template)\n}\n\n// Export the parser\nexport function parse(template: string) {\n  return baseParse(template)\n}\n```\n\nKeep these interfaces in the compiler-sfc side.\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/compileTemplate.ts\n```\n\n`~/packages/compiler-sfc/compileTemplate.ts`\n\n```ts\nimport { TemplateChildNode } from '../compiler-core'\n\nexport interface TemplateCompiler {\n  compile(template: string): string\n  parse(template: string): { children: TemplateChildNode[] }\n}\n```\n\nThen, just implement the parser.\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { ElementNode, NodeTypes, SourceLocation } from '../compiler-core'\nimport * as CompilerDOM from '../compiler-dom'\nimport { TemplateCompiler } from './compileTemplate'\n\nexport interface SFCParseOptions {\n  filename?: string\n  sourceRoot?: string\n  compiler?: TemplateCompiler\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor\n}\n\nexport const DEFAULT_FILENAME = 'anonymous.vue'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  }\n\n  const ast = compiler.parse(source)\n  ast.children.forEach(node => {\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    switch (node.tag) {\n      case 'template': {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock\n        break\n      }\n      case 'script': {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock\n        descriptor.script = scriptBlock\n        break\n      }\n      case 'style': {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock)\n        break\n      }\n      default: {\n        break\n      }\n    }\n  })\n\n  return { descriptor }\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag\n\n  let { start, end } = node.loc\n  start = node.children[0].loc.start\n  end = node.children[node.children.length - 1].loc.end\n  const content = source.slice(start.offset, end.offset)\n\n  const loc = { source: content, start, end }\n  const block: SFCBlock = { type, content, loc }\n\n  return block\n}\n```\n\nI think it's easy for everyone who has implemented the parser so far. Let's actually parse SFC in the plugin.\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport { parse } from '../../compiler-sfc'\n\nexport default function vitePluginChibivue(): Plugin {\n  //.\n  //.\n  //.\n  return {\n    //.\n    //.\n    //.\n    transform(code, id) {\n      if (!filter(id)) return\n      const { descriptor } = parse(code, { filename: id })\n      console.log(\n        '🚀 ~ file: index.ts:14 ~ transform ~ descriptor:',\n        descriptor,\n      )\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\nThis code runs in the process where Vite is running, which means it is executed in Node, so I think the console output is displayed in the terminal.\n\n![SFC descriptor before parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-before.png)\n\n/_ Omitted for brevity _/\n\n![SFC descriptor after parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-after.png)\n\nIt seems that parsing was successful. Great job!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler2)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/092-compile-sfc-template.md",
    "content": "# Compiling the template block\n\n## Switching the Compiler\n\n`descriptor.script.content` and `descriptor.template.content` contain the source code of each section.  \nLet's compile them successfully. Let's start with the template section.  \nWe already have the template compiler.  \nHowever, as you can see from the following code,\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n```\n\nThis assumes that it will be used with the Function constructor, so it includes the `return` statement at the beginning.  \nIn the SFC compiler, we only want to generate the render function, so let's make it possible to branch with compiler options.  \nLet's make it possible to receive options as the second argument of the compiler and specify a flag called `isBrowser`.  \nWhen this variable is `true`, it outputs code that assumes it will be `new` on the runtime, and when it is `false`, it simply generates code.\n\n```sh\npwd # ~\ntouch packages/compiler-core/options.ts\n```\n\n`packages/compiler-core/options.ts`\n\n```ts\nexport type CompilerOptions = {\n  isBrowser?: boolean\n}\n```\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(template, defaultOption)\n}\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult, option)\n  return code\n}\n```\n\n`~/packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n```\n\nI also added the import statement. I changed it to add the generated source code to the `output` array.\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\\n\")\n\n      const { descriptor } = parse(code, { filename: id })\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { render }`)\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\n## Issues\n\nNow you should be able to compile the render function. Let's check it in the browser's source.\n\nHowever, there is a small problem.\n\nWhen binding data to the template, I think you are using the `with` statement. \\\nHowever, due to the nature of Vite handling ESM, it cannot process code that only works in non-strict mode (sloppy mode) and cannot handle `with` statements.  \nSo far, it hasn't been a problem because I was simply passing code (strings) containing `with` statements to the Function constructor and making it a function in the browser, but now it throws an error. \\\nYou should see an error like this:\n\n> Strict mode code may not include a with statement\n\nThis is also described in the Vite official documentation as a troubleshooting tip.\n\n[Syntax Error / Type Error Occurs (Vite)](https://vite.dev/guide/troubleshooting.html#syntax-error-type-error-occurs)\n\nAs a temporary solution, let's try to generate code that does not include the `with` statement when it is not in browser mode.\n\nSpecifically, for the data to be bound, let's try to control it by adding the prefix `_ctx.` instead of using the `with` statement.\\\nSince this is a temporary solution, it is not very strict, but I think it will work generally.\\\n(The proper solution will be implemented in a later chapter.)\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  // Generate code that does not include the `with` statement when `isBrowser` is false\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n    ${option.isBrowser ? 'with (_ctx) {' : ''}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? '}' : ''}\n}`\n}\n\n// .\n// .\n// .\n\nconst genNode = (\n  node: TemplateChildNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option)\n    case NodeTypes.TEXT:\n      return genText(node)\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (\n  el: ElementNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop, option))\n    .join(', ')}}, [${el.children.map(it => genNode(it, option)).join(', ')}])`\n}\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${\n            option.isBrowser ? '' : '_ctx.' // -------------------- Here\n          }${prop.exp}`\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n\n// .\n// .\n// .\n\nconst genInterpolation = (\n  node: InterpolationNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? '' : '_ctx.'}${node.content}` // ------------ Here\n}\n```\n\n![Compiled SFC template render result](/figures/10-minimum-example/compile-sfc-template/compiled-render-result.png)\n\nIt seems that it was compiled successfully. All that's left is to extract the script in the same way and put it into the default exports.\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler3)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/093-compile-sfc-script.md",
    "content": "# Compiling the script block\n\n## What we want to do\n\nNow, the original script section of SFC looks like this:\n\n```ts\nexport default {\n  setup() {},\n}\n```\n\nI want to extract only the following part:\n\n```ts\n  {\n  setup() {},\n}\n```\n\nIs there any way to do this?\n\nIf I can extract this part, I can mix it nicely with the previously generated render function and export it as follows:\n\n```ts\nconst _sfc_main = {\n  setup() {},\n}\n\nexport default { ..._sfc_main, render }\n```\n\n## Using external libraries\n\nTo achieve the above, I will use the following two libraries:\n\n- @babel/parser\n- magic-string\n\n### Babel\n\nhttps://babeljs.io\n\n[What is Babel](https://babeljs.io/docs)\n\nYou may have heard of Babel if you are familiar with JavaScript. \\\nBabel is a toolchain used to convert JavaScript into backward-compatible versions. \\\nIn simple terms, it is a compiler (transpiler) from JS to JS. \n\nIn this case, I will use Babel not only as a compiler but also as a parser. \\\nBabel has an internal parser for converting to AST, as it plays the role of a compiler. \n\nAST stands for Abstract Syntax Tree, which is a representation of JavaScript code. \\\nYou can find the AST specification here (https://github.com/estree/estree). \\\nAlthough you can refer to the GitHub md file, I will briefly explain AST in JavaScript. \\\nThe entire program is represented by a Program AST node, which contains an array of statements (represented using TS interfaces for clarity).\n\n```ts\ninterface Program {\n  body: Statement[]\n}\n```\n\nStatement represents a \"statement\" in JavaScript, which is a collection of statements. \\\nExamples include \"variable declaration statement,\" \"if statement,\" \"for statement,\" and \"block statement.\"\n\n```ts\ninterface Statement {}\n\ninterface VariableDeclaration extends Statement {\n  /* omitted */\n}\n\ninterface IfStatement extends Statement {\n  /* omitted */\n}\n\ninterface ForStatement extends Statement {\n  /* omitted */\n}\n\ninterface BlockStatement extends Statement {\n  body: Statement[]\n}\n// There are many more\n```\n\nStatements usually have an \"expression\" in most cases. \\\nAn expression is something that can be assigned to a variable. \\\nExamples include \"object,\" \"binary operation,\" and \"function call.\"\n\n```ts\ninterface Expression {}\n\ninterface BinaryExpression extends Expression {\n  operator: '+' | '-' | '*' | '/' // There are many more, but omitted\n  left: Expression\n  right: Expression\n}\n\ninterface ObjectExpression extends Expression {\n  properties: Property[] // omitted\n}\n\ninterface CallExpression extends Expression {\n  callee: Expression\n  arguments: Expression[]\n}\n\n// There are many more\n```\n\nIf we consider an if statement, it has the following structure:\n\n```ts\ninterface IfStatement extends Statement {\n  test: Expression // condition\n  consequent: Statement // statements to be executed if the condition is true\n  alternate: Statement | null // statements to be executed if the condition is false\n}\n```\n\nIn this way, JavaScript syntax is parsed into the AST mentioned above. \\\nI think this explanation is easy to understand for those who have already implemented the template compiler for chibivue. (It's the same thing)\n\nThe reason why I use Babel is twofold.\\\nFirst, it's simply because it's cumbersome. \\\nIf you have implemented a parser before, it may be technically possible to implement a JS parser while referring to estree. \\\nHowever, it is very cumbersome, and it is not very important for the purpose of \"deepening understanding of Vue\" in this case. \\\nThe other reason is that the official Vue also uses Babel for this part.\n\n### magic-string\n\nhttps://github.com/rich-harris/magic-string\n\nThere is another library I want to use. \\\nThis library is also used by the official Vue. \\\nIt is a library that makes string manipulation easier.\n\n```ts\nconst input = 'Hello'\nconst s = new MagicString(input)\n```\n\nYou can generate an instance like this and use the convenient methods provided by the instance to manipulate strings. \\\nHere are some examples:\n\n```ts\ns.append('!!!') // Append to the end\ns.prepend('message: ') // Prepend to the beginning\ns.overwrite(9, 13, 'こんにちは') // Overwrite within a range\n```\n\nThere is no need to use it forcefully, but I will use it to align with the official Vue.\n\nWhether it's Babel or magic-string, you don't need to understand the actual usage at this point. \\\nI will explain and align the implementation later, so it's okay to have a rough understanding for now.\n\n## Rewriting the default export of the script\n\nTo recap the current goal:\n\n```ts\nexport default {\n  setup() {},\n  // Other options\n}\n```\n\nI want to rewrite the code above to:\n\n```ts\nconst _sfc_main = {\n  setup() {},\n  // Other options\n}\n\nexport default { ..._sfc_main, render }\n```\n\nIn other words, if I can extract the export target from the original code's export statement and assign it to a variable called `_sfc_main`, I will achieve the goal.\n\nFirst, let's install the necessary libraries.\n\n```sh\npwd # ~\npnpm add @babel/parser magic-string\n```\n\nCreate a file called \"rewriteDefault.ts\".\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/rewriteDefault.ts\n```\n\nMake sure that the function \"rewriteDefault\" can receive the target source code as \"input\" and the variable name to be bound as \"as\".  \\\nReturn the converted source code as the return value.\n\n`~/packages/compiler-sfc/rewriteDefault.ts`\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // TODO:\n  return ''\n}\n```\n\nFirst, let's handle the case where the export declaration does not exist. \\\nSince there is no export, bind an empty object and finish.\n\n```ts\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`\n  }\n\n  // TODO:\n  return ''\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input)\n}\n```\n\nHere comes the Babel parser and magic-string.\n\n```ts\nimport { parse } from '@babel/parser'\nimport MagicString from 'magic-string'\n// .\n// .\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  const s = new MagicString(input)\n  const ast = parse(input, {\n    sourceType: 'module',\n  }).program.body\n  // .\n  // .\n}\n```\n\nFrom here, we will manipulate the string `s` based on the JavaScript AST (Abstract Syntax Tree) obtained by the Babel parser. \\\nAlthough it is a bit long, I will provide additional explanations in the comments in the source code.\\\nBasically, we traverse the AST and write conditional statements based on the `type` property, and manipulate the string `s` using the methods of `magic-string`.\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  ast.forEach(node => {\n    // In case of default export\n    if (node.type === 'ExportDefaultDeclaration') {\n      if (node.declaration.type === 'ClassDeclaration') {\n        // If it is `export default class Hoge {}`, replace it with `class Hoge {}`\n        s.overwrite(node.start!, node.declaration.id.start!, `class `)\n        // Then, add code like `const ${as} = Hoge;` at the end.\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`)\n      } else {\n        // For other default exports, replace the declaration part with a variable declaration.\n        // eg 1) `export default { setup() {}, }`  ->  `const ${as} = { setup() {}, }`\n        // eg 2) `export default Hoge`  ->  `const ${as} = Hoge`\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)\n      }\n    }\n\n    // There may be a default export in the declaration even in the case of named export.\n    // Mainly 3 patterns\n    //   1. In the case of declaration like `export { default } from \"source\";`\n    //   2. In the case of declaration like `export { hoge as default }` from 'source'\n    //   3. In the case of declaration like `export { hoge as default }`\n    if (node.type === 'ExportNamedDeclaration') {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === 'ExportSpecifier' &&\n          specifier.exported.type === 'Identifier' &&\n          specifier.exported.name === 'default'\n        ) {\n          // If there is a keyword `from`\n          if (node.source) {\n            if (specifier.local.name === 'default') {\n              // 1. In the case of declaration like `export { default } from \"source\";`\n              // In this case, extract it into an import statement and give it a name, then bind it to the final variable.\n              // eg) `export { default } from \"source\";`  ->  `import { default as __VUE_DEFAULT__ } from 'source'; const ${as} = __VUE_DEFAULT__`\n              const end = specifierEnd(input, specifier.local.end!, node.end!)\n              s.prepend(\n                `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`,\n              )\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`)\n              continue\n            } else {\n              // 2. In the case of declaration like `export { hoge as default }` from 'source'\n              // In this case, rewrite all specifiers as they are in the import statement, and bind the variable that is as default to the final variable.\n              // eg) `export { hoge as default } from \"source\";`  ->  `import { hoge } from 'source'; const ${as} = hoge\n              const end = specifierEnd(\n                input,\n                specifier.exported.end!,\n                node.end!,\n              )\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              )\n\n              // 3. In the case of declaration like `export { hoge as default }`\n              // In this case, simply bind it to the final variable.\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = ${specifier.local.name}`)\n              continue\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!)\n          s.overwrite(specifier.start!, end, ``)\n          s.append(`\\nconst ${as} = ${specifier.local.name}`)\n        }\n      }\n    }\n  })\n  return s.toString()\n}\n\n// Calculate the end of the declaration statement\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false\n  let oldEnd = end\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++\n    } else if (input.charAt(end) === ',') {\n      end++\n      hasCommas = true\n      break\n    } else if (input.charAt(end) === '}') {\n      break\n    }\n  }\n  return hasCommas ? end : oldEnd\n}\n```\n\nNow you can rewrite the default export. \\\nLet's try using it in a plugin.\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse, rewriteDefault } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n\n      const { descriptor } = parse(code, { filename: id })\n\n      // --------------------------- From here\n      const SFC_MAIN = '_sfc_main'\n      const scriptCode = rewriteDefault(\n        descriptor.script?.content ?? '',\n        SFC_MAIN,\n      )\n      outputs.push(scriptCode)\n      // --------------------------- To here\n\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { ...${SFC_MAIN}, render }`) // Here\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\nBefore that, let's make a small modification.\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n  // Add the component's render option to the instance\n  const { render } = component\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n}\n```\n\nNow you should be able to render!!!\n\n![Rendered SFC script result](/figures/10-minimum-example/compile-sfc-script/render-sfc-result.png)\n\nThe styles are not applied because they are not supported, but now you can render the component."
  },
  {
    "path": "book/online-book/src/10-minimum-example/094-compile-sfc-style.md",
    "content": "# Compile Style Blocks\n\n## Virtual Modules\n\nLet's also support styles. \\\nIn Vite, you can import CSS files by using the `.css` extension.\n\n```js\nimport 'app.css'\n```\n\nWe will implement this by using Vite's virtual modules. \\\nVirtual modules allow you to keep non-existent files in memory as if they exist. \\\nYou can use the `load` and `resolveId` options to implement virtual modules.\n\n```ts\nexport default function myPlugin() {\n  const virtualModuleId = 'virtual:my-module'\n\n  return {\n    name: 'my-plugin', // Required, displayed in warnings and errors\n    resolveId(id) {\n      if (id === virtualModuleId) {\n        return virtualModuleId\n      }\n    },\n    load(id) {\n      if (id === virtualModuleId) {\n        return `export const msg = \"from virtual module\"`\n      }\n    },\n  }\n}\n```\n\nUsing this mechanism, we will load the style block of an SFC as a virtual CSS file.  \nAs mentioned earlier, in Vite, importing a file with the `.css` extension is sufficient, so we will consider creating a virtual module named `${SFC file name}.css`.\n\n## Implementing a Virtual Module with the Content of the SFC Style Block\n\nFor this example, let's consider a file named \"App.vue\" and implement a virtual module named \"App.vue.css\" for its style section.  \nThe process is straightforward: when a file named `**.vue.css` is loaded, we will retrieve the SFC using `fs.readFileSync` from the file path without the `.css` (i.e., the original Vue file), parse it to extract the content of the style tag, and return that content as the code.\n\n\n```ts\nexport default function vitePluginChibivue(): Plugin {\n  //  ,\n  //  ,\n  //  ,\n  return {\n    //  ,\n    //  ,\n    //  ,\n    resolveId(id) {\n      // This ID is a non-existent path, but we handle it virtually in load, so we return the ID to indicate that it can be loaded\n      if (id.match(/\\.vue\\.css$/)) return id\n\n      // For IDs that are not returned here, if the file actually exists, the file will be resolved, and if it does not exist, an error will be thrown\n    },\n    load(id) {\n      // Handling when .vue.css is loaded (when import is declared and loaded)\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, '')\n        const content = fs.readFileSync(filename, 'utf-8') // Retrieve the SFC file normally\n        const { descriptor } = parse(content, { filename }) // Parse the SFC\n\n        // Join the content and return it as the result\n        const styles = descriptor.styles.map(it => it.content).join('\\n')\n        return { code: styles }\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n      outputs.push(`import '${id}.css'`) // Declare the import statement for ${id}.css\n      //  ,\n      //  ,\n      //  ,\n    },\n  }\n}\n```\n\nNow, let's check in the browser.\n\n![Virtual CSS module request in the browser](/figures/10-minimum-example/compile-sfc-style/load-virtual-css-module.png)\n\nIt seems that the styles are applied correctly.\n\nIn the browser, you can see that the CSS is imported and a `.vue.css` file is generated virtually.\n\n![Loaded CSS module in the browser](/figures/10-minimum-example/compile-sfc-style/loaded-css-in-browser.png)\n![Generated Vue CSS module](/figures/10-minimum-example/compile-sfc-style/generated-vue-css-module.png)\n\nNow you can use SFC!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler4)\n"
  },
  {
    "path": "book/online-book/src/10-minimum-example/100-break.md",
    "content": "# Take a break\n\n<KawaikoNote variant=\"surprise\" title=\"Great job!\">\n\nCongratulations on completing the Minimal Example Section!\nYou've now experienced the core parts of Vue.js.\n\n</KawaikoNote>\n\n## Minimal Example section is over!\n\nAt the beginning, I mentioned that this book is divided into several sections, and the first section, \"Minimal Example Section,\" is now complete. Well done!\\\nIf you are interested in Virtual DOM or patch rendering, you can move on to the Basic Virtual DOM section. \\\nIf you want to extend components further, there is the Basic Component section. If you are interested in richer expressions in templates (such as directives), you can explore the Basic Template Compiler section. \\\nIf you are interested in script setup or compiler macros, you can proceed to the Basic SFC Compiler section. (Of course, you can do them all if you want!!)\\\nAbove all, the \"Minimal Example Section\" is also a respectable section, so if you feel like, \"I don't need to know too deeply, but I want to get a general idea,\" then you are good to go up to this point.\n\n## What have we achieved so far?\n\nFinally, let's reflect on what we have done and what we have achieved in the Minimal Example section.\n\n## We now know what we are looking at and where it belongs\n\nFirst, through the initial developer interface called createApp, we understood how the (web app) developer and the world of Vue are connected.  \nSpecifically, starting from the refactoring we did at the beginning, you should now understand the foundation of Vue's directory structure, its dependencies, and where the developers are working on.  \nLet's compare the current directory and the directory of vuejs/core.\n\nchibivue\n![Minimum example implementation artifacts](/figures/10-minimum-example/break/minimum-example-artifacts.png)\n\n\\*The original code is too large to fit in a screenshot, so it is omitted.\n\nhttps://github.com/vuejs/core\n\nEven though it's small, you should now be able to read and understand the roles and contents of each file to some extent. \\\nI hope you will also challenge yourself to read the source code of the parts we haven't covered this time. (You should be able to read it little by little!)\n\n## We now know how declarative UI is achieved\n\nThrough the implementation of the h function, we understood how declarative UI is achieved.\n\n```ts\n// Internally, it generates an object like {tag, props, children} and performs DOM operations based on it\nh('div', { id: 'my-app' }, [\n  h('p', {}, ['Hello!']),\n  h(\n    'button',\n    {\n      onClick: () => {\n        alert('hello')\n      },\n    },\n    ['Click me!'],\n  ),\n])\n```\n\nThis is where something like Virtual DOM first appears.\n\n## We now know what the Reactivity System is and how to dynamically update the screen\n\nWe understood the implementation of Vue's unique feature, the Reactivity System, how it works, and what it actually is.\n\n```ts\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nfunction reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, {\n    get(target: object, key: string | symbol, receiver: object) {\n      track(target, key)\n      return Reflect.get(target, key, receiver)\n    },\n\n    set(\n      target: object,\n      key: string | symbol,\n      value: unknown,\n      receiver: object,\n    ) {\n      Reflect.set(target, key, value, receiver)\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n```ts\nconst component = {\n  setup() {\n    const state = reactive({ count: 0 }) // create proxy\n\n    const increment = () => {\n      state.count++ // trigger\n    }\n\n    ;() => {\n      return h('p', {}, `${state.count}`) // track\n    }\n  },\n}\n```\n\n## We now know what Virtual DOM is, why it is beneficial, and how to implement it\n\nAs an improvement to rendering using the h function, we understood the efficient rendering method using Virtual DOM through comparison.\n\n```ts\n// Interface for Virtual DOM\nexport interface VNode<HostNode = any> {\n  type: string | typeof Text | object\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined\n}\n\n// First, the render function is called\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  // The first time, n1 is null. In this case, each process runs mount\n  patch(null, vnode, container)\n}\n\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\n// From the second time onwards, the previous VNode and the current VNode are passed to the patch function to update the differences\nconst nextVNode = component.render()\npatch(prevVNode, nextVNode)\n```\n\nI understood how the structure of components and the interaction between components are achieved.\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component\n\n  vnode: VNode\n  subTree: VNode\n  next: VNode | null\n  effect: ReactiveEffect\n  render: InternalRenderFunction\n  update: () => void\n\n  propsOptions: Props\n  props: Data\n  emit: (event: string, ...args: any[]) => void\n\n  isMounted: boolean\n}\n```\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\nI understood what the compiler is and how the template functionality is implemented.\n\nBy understanding what the compiler is and implementing the template compiler, I gained an understanding of how to achieve a more raw HTML-like implementation and how to implement Vue-specific features such as Mustache syntax.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\nI understood how to achieve the SFC compiler through the Vite plugin.\n\nBy implementing the template compiler and utilizing it through the Vite plugin, I gained an understanding of how to implement an original file format that combines script, template, and style into one file.\\\nI also learned about what can be done with Vite plugins, as well as transform and virtual modules.\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n## About the Future\n\nFrom now on, in order to make it more practical, we will go into more detail in each part.  \nI will explain a little about what to do and how to proceed (policy) for each part.\n\n### What to do\n\nFrom here, it will be divided into 5 parts + 1 appendix.\n\n- Basic Virtual DOM Part\n  - Implementation of the scheduler\n  - Implementation of unsupported patches (mainly related to attributes)\n  - Support for Fragment\n- Basic Reactivity System Part\n  - ref API\n  - computed API\n  - watch API\n- Basic Component System Part\n  - provide/inject\n  - lifecycle hooks\n- Basic Template Compiler Part\n  - v-on\n  - v-bind\n  - v-for\n  - v-model\n- Basic SFC Compiler Part\n  - Basics of SFC\n  - script setup\n  - compiler macro\n- Web Application Essentials Part (Appendix)\n\nThis part is an appendix. \\\nIn this part, we will implement libraries that are frequently used together with Vue in web development. \n\n- store\n- route\n\nWe will cover the above two, but feel free to implement other things that come to mind!\n\n### Policy\n\nIn the Minimal Example part, we explained the implementation steps in quite detail. \\\nBy now, if you have implemented it, you should be able to read the source code of the original Vue. \nTherefore, from now on, the explanations will be kept to a rough policy, and you will implement the actual code while reading the original code or thinking on your own. \\\n(N-no, it's not that I'm getting lazy to write in detail or anything like that!) \\\nWell, it's fun to implement it as the book says, but once it starts to take shape, it's more fun to do it yourself and it leads to a deeper understanding. \\\nFrom here on, please consider this book as a kind of guideline, and the main content is in the original Vue source code!\n\n<KawaikoNote variant=\"funny\" title=\"The real journey begins!\">\n\nWith the knowledge you've gained so far, you can now read the Vue.js source code.\nFeel free to proceed to the sections that interest you, or dive into the original code - enjoy it your way!\n\n</KawaikoNote>\n"
  },
  {
    "path": "book/online-book/src/20-basic-virtual-dom/010-patch-keyed-children.md",
    "content": "# key attribute and patch rendering (Basic Virtual DOM section start)\n\n## Critical bug\n\nActually, there is a critical bug in the current patch rendering of chibivue.  \nWhen implementing patch rendering,\n\n> Regarding patchChildren, it is necessary to handle dynamically sized child elements by adding attributes such as key.\n\nDo you remember saying that?\n\nLet's see what kind of problem actually occurs.\nIn the current implementation, patchChildren is implemented as follows:\n\n```ts\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\nThis loops based on the length of c2 (i.e., the next vnode).\nIn other words, it basically only works when c1 and c2 are the same.\n\n![Index-based patch with equal lengths](/figures/20-basic-virtual-dom/patch-keyed-children/same-length-index-patch.svg)\n\nFor example, let's consider the case where elements are removed.\nSince the patch loop is based on c2, the patch for the fourth element will not be performed.\n\n![Deleted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/deleted-child-bug.svg)\n\nWhen it becomes like this, the first to third elements are simply updated, and the fourth element remains as the one from c1 that is not removed.\n\nLet's see it in action.\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ list: ['a', 'b', 'c', 'd'] })\n    const updateList = () => {\n      state.list = ['e', 'f', 'g']\n    }\n\n    return () =>\n      h('div', { id: 'app' }, [\n        h(\n          'ul',\n          {},\n          state.list.map(item => h('li', {}, [item])),\n        ),\n        h('button', { onClick: updateList }, ['update']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nWhen you click the update button, it should look like this:\n\n![Stale child bug rendered in the browser](/figures/20-basic-virtual-dom/patch-keyed-children/stale-child-bug-result.png)\n\nAlthough the list should have been updated to `[\"e\", \"f\", \"g\"]`, \"d\" remains.\n\nAnd actually, the problem is not just this. Let's consider the case where elements are inserted.\nCurrently, since the loop is based on c2, it becomes like this:\n\n![Inserted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-index-bug.svg)\n\nHowever, in reality, \"new element\" was inserted, and the comparison should be made between each li 1, li 2, li 3, and li 4 of c1 and c2.\n\n![Inserted child handled with keyed matching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg)\n\nWhat these two problems have in common is that \"the node that needs to be treated as the same in c1 and c2 cannot be determined\".  \nTo solve this, it is necessary to assign a key to the elements and patch based on that key.  \nNow, let's take a look at the explanation of the key attribute in the Vue documentation.\n\n> The special attribute key is primarily used as a hint for Vue's Virtual DOM algorithm to identify VNodes when diffing the new list of nodes against the old list.\n\nhttps://v3-migration.vuejs.org/breaking-changes/key-attribute.html\n\nAs expected, right? You may have heard the advice \"do not use index as the key for v-for\", but at this point, the key is implicitly set to the index, which is why the above problems occur. (The loop is based on the length of c2, and patching is done based on that index)\n\n## Patch based on the key attribute\n\nAnd the function that implements these is `patchKeyedChildren`. (Let's search for it in the original Vue.)\n\nThe approach is to first generate a map of keys and indexes for the new nodes.\n\n```ts\nlet i = 0\nconst l2 = c2.length\nconst e1 = c1.length - 1 // end index of prev node\nconst e2 = l2 - 1 // end index of next node\n\nconst s1 = i // start index of prev node\nconst s2 = i // start index of next node\n\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = s2; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n```\n\nIn the original Vue, this `patchKeyedChildren` is divided into five parts:\n\n1. sync from start\n2. sync from end\n3. common sequence + mount\n4. common sequence + unmount\n5. unknown sequence\n\nHowever, the last part, `unknown sequence`, is the only one that is functionally necessary, so we will start by reading and implementing that part.\n\nFirst, forget about moving elements and patch VNodes based on the key.\nUsing the `keyToNewIndexMap` we created earlier, calculate the pairs of n1 and n2 and patch them.\nAt this point, if there are new elements to be mounted or if there is a need to unmount, perform those operations as well.\n\nRoughly speaking, it looks like this ↓ (I'm skipping a lot of details. Please read vuejs/core's renderer.ts for more details.)\n\n```ts\nconst toBePatched = e2 + 1\nconst newIndexToOldIndexMap = new Array(toBePatched) // map of new index to old index\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// Loop based on e1 (old len)\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // If it does not exist in the new one, unmount it\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // Form the map\n    patch(prevChild, c2[newIndex] as VNode, container) // Patch\n  }\n}\n\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  if (newIndexToOldIndexMap[i] === 0) {\n    // If the map does not exist (remains the initial value), it means it needs to be newly mounted. (In fact, it exists and is not in the old one)\n    patch(null, nextChild, container, anchor)\n  }\n}\n```\n\n## Moving Elements\n\n### Method\n\n#### Node.insertBefore\n\nCurrently, we only update each element based on key matching, so if an element is moved, we need to write code to move it to the desired position.\n\nFirst, let's talk about how to move elements. We specify the anchor in the `insert` function of `nodeOps`. The anchor, as the name suggests, is an anchor point, and if you look at the `insert` method implemented in runtime-dom, you can see that it is implemented with the `insertBefore` method.\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // .\n  // .\n  // .\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\nBy passing the node as the second argument to this method, the node will be inserted right before that node.  \nhttps://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore\n\nWe use this method to actually move the DOM.\n\n#### LIS (Longest Increasing Subsequence)\n\nNow, let's talk about how to write the algorithm for moving. This part is a bit more complicated.  \nDOM operations are much more costly compared to running JavaScript, so we want to minimize the number of unnecessary moves as much as possible.  \nThat's where we use the \"Longest Increasing Subsequence\" (LIS) algorithm.  \nThis algorithm finds the longest increasing subsequence in an array.  \nAn increasing subsequence is a subsequence in which the elements are in increasing order.  \nFor example, given the following array:\n\n```\n[2, 4, 1, 7, 5, 6]\n```\n\nThere are several increasing subsequences:\n\n```\n[2, 4]\n[2, 5]\n.\n.\n[2, 4, 7]\n[2, 4, 5]\n.\n.\n[2, 4, 5, 6]\n.\n.\n[1, 7]\n.\n.\n[1, 5, 6]\n```\n\nThese are the subsequences where the elements are increasing. The longest one is the \"Longest Increasing Subsequence\".  \nIn this case, `[2, 4, 5, 6]` is the longest increasing subsequence. And in Vue, the indices corresponding to 2, 4, 5, and 6 are treated as the result array (i.e., `[0, 1, 4, 5]`).\n\nBy the way, here's an example function:\n\n```ts\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice()\n  const result = [0]\n  let i, j, u, v, c\n  const len = arr.length\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i]\n    if (arrI !== 0) {\n      j = result[result.length - 1]\n      if (arr[j] < arrI) {\n        p[i] = j\n        result.push(i)\n        continue\n      }\n      u = 0\n      v = result.length - 1\n      while (u < v) {\n        c = (u + v) >> 1\n        if (arr[result[c]] < arrI) {\n          u = c + 1\n        } else {\n          v = c\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1]\n        }\n        result[u] = i\n      }\n    }\n  }\n  u = result.length\n  v = result[u - 1]\n  while (u-- > 0) {\n    result[u] = v\n    v = p[v]\n  }\n  return result\n}\n```\n\nWe will use this function to calculate the longest increasing subsequence from `newIndexToOldIndexMap`, and based on that, we will use `insertBefore` to insert the other nodes.\n\n### Concrete Example\n\nHere's a concrete example to make it easier to understand.\n\nLet's consider two arrays of VNodes, `c1` and `c2`. `c1` represents the state before the update, and `c2` represents the state after the update. Each VNode has a `key` attribute (in reality, it holds more information).\n\n```js\nc1 = [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }]\nc2 = [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }]\n```\n\nFirst, let's generate `keyToNewIndexMap` based on `c2` (a map of keys to indices in `c2`).\n※ This is the code introduced earlier.\n\n```ts\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = 0; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n\n// keyToNewIndexMap = { a: 0, b: 1, d: 2, c: 3 }\n```\n\nNext, let's generate `newIndexToOldIndexMap`.\n※ This is the code introduced earlier.\n\n```ts\n// Initialization\n\nconst toBePatched = c2.length\nconst newIndexToOldIndexMap = new Array(toBePatched) // Map of new indices to old indices\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// newIndexToOldIndexMap = [0, 0, 0, 0]\n```\n\n```ts\n// Perform patch and generate newIndexToOldIndexMap for move\n\n// Loop based on e1 (old len)\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // If it doesn't exist in the new array, unmount it\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // Form the map\n    patch(prevChild, c2[newIndex] as VNode, container) // Perform patch\n  }\n}\n\n// newIndexToOldIndexMap = [1, 2, 4, 3]\n```\n\nThen, obtain the longest increasing subsequence from the obtained `newIndexToOldIndexMap` (new implementation starts here).\n\n```ts\nconst increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)\n// increasingNewIndexSequence  = [0, 1, 3]\n```\n\n```ts\nj = increasingNewIndexSequence.length - 1\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  const anchor =\n    nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor // ※ parentAnchor はとりあえず引数で受け取った anchor だと思ってもらえれば。\n\n  if (newIndexToOldIndexMap[i] === 0) {\n    // newIndexToOldIndexMap は初期値が 0 なので、0 の場合は古い要素への map が存在しない、つまり新しい要素だというふうに判定している。\n    patch(null, nextChild, container, anchor)\n  } else {\n    // i と increasingNewIndexSequence[j] が一致しなければ move する\n    if (j < 0 || i !== increasingNewIndexSequence[j]) {\n      move(nextChild, container, anchor)\n    } else {\n      j--\n    }\n  }\n}\n```\n\n### Let's implement it.\n\nNow that we have explained the approach in detail, let's actually implement `patchKeyedChildren`. Here is a summary of the steps:\n\n1. Prepare the `anchor` for bucket relay (used for inserting in `move`).\n2. Create a map of keys and indices based on `c2`.\n3. Create a map of indices in `c2` and `c1` based on the key map.  \n   At this stage, perform the patching process in both `c1`-based and `c2`-based loops (excluding `move`).\n4. Find the longest increasing subsequence based on the map obtained in step 3.\n5. Perform `move` based on the subsequence obtained in step 4 and `c2`.\n\nYou can refer to the original Vue implementation or the chibivue implementation for guidance. (I recommend reading the original Vue implementation while following along.)\n"
  },
  {
    "path": "book/online-book/src/20-basic-virtual-dom/020-bit-flags.md",
    "content": "# Representation of VNode using bits\n\n## Representing the types of VNodes using bits\n\nThere are various types of VNodes. For example, the ones currently implemented include:\n\n- component node\n- element node\n- text node\n- whether the child element is text or not\n- whether the child element is an array or not\n\nAnd in the future, more types of VNodes will be implemented. For example, slot, keep-alive, suspense, teleport, etc.\n\nCurrently, branching is done using conditions such as `type === Text`, `typeof type === \"string\"`, `typeof type === \"object\"`, etc.\n\nChecking these conditions one by one is inefficient, so let's try representing them using bits, following the implementation of the original. In Vue, these bits are called \"ShapeFlags\". As the name suggests, they represent the shape of the VNode. (Strictly speaking, in Vue, ShapeFlags and symbols such as Text and Fragment are used to determine the type of VNode.)\nhttps://github.com/vuejs/core/blob/main/packages/shared/src/shapeFlags.ts\n\nBit flags refer to treating each bit of a number as a specific flag.\n\nLet's consider the following VNode as an example:\n\n```ts\nconst vnode = {\n  type: 'div',\n  children: [\n    { type: 'p', children: ['hello'] },\n    { type: 'p', children: ['hello'] },\n  ],\n}\n```\n\n![ShapeFlags pack VNode shape into bits](/figures/20-basic-virtual-dom/bit-flags/shape-flag-overview.svg)\n\nFirst, the initial value of the flag is 0. (For simplicity, this explanation is given using 8 bits.)\n\n```ts\nlet shape = 0b0000_0000\n```\n\nNow, this VNode is an element and has an array of children, so the ELEMENT flag and the ARRAY_CHILDREN flag are set.\n\n```ts\nshape = shape | ShapeFlags.ELEMENT | ELEMENT.ARRAY_CHILDREN // 0x00010001\n```\n\nWith this, we can represent the information that this VNode is an element and has an array of children using just one number called \"shape\". We can efficiently manage the types of VNodes by using this in branching in the renderer or other parts of the code.\n\n```ts\nif (vnode.shape & ShapeFlags.ELEMENT) {\n  // Processing when vnode is an element\n}\n```\n\nSince we are not implementing all ShapeFlags this time, please try implementing the following as an exercise:\n\n```ts\nexport const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n```\n\nHere's what you need to do:\n\n- Define the flags in shared/shapeFlags.ts\n- Define the shape in runtime-core/vnode.ts\n  ```ts\n  export interface VNode<HostNode = any> {\n    shapeFlag: number\n  }\n  ```\n  Add this and calculate the flag in functions like createVNode.\n- Implement branching logic based on the shape in the renderer.\n\nThat's it for the explanation of this chapter. Let's start implementing it!\n"
  },
  {
    "path": "book/online-book/src/20-basic-virtual-dom/030-scheduler.md",
    "content": "# Scheduler\n\n## Scheduling Effects\n\nFirst, take a look at this code:\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      message: 'Hello World',\n    })\n    const updateState = () => {\n      state.message = 'Hello ChibiVue!'\n      state.message = 'Hello ChibiVue!!'\n    }\n\n    return () => {\n      console.log('😎 rendered!')\n\n      return h('div', { id: 'app' }, [\n        h('p', {}, [`message: ${state.message}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nWhen the button is clicked, the `set` function is called twice on `state.message`, so naturally, the `trigger` function will be executed twice as well. This means that the Virtual DOM will be computed twice and the patching will be performed twice.\n\n![Effect result before scheduler batching](/figures/20-basic-virtual-dom/scheduler/non-scheduled-effect.png)\n\nHowever, in reality, patching only needs to be done once, during the second trigger.  \nTherefore, we will implement a scheduler. A scheduler is responsible for managing the execution order and control of tasks. One of the roles of the Vue scheduler is to manage reactive effects in a queue and consolidate them if possible.\n\n## Scheduling with Queue Management\n\nSpecifically, we will have a queue to manage jobs. Each job has an ID, and when a new job is enqueued, if there is already a job with the same ID in the queue, it will be overwritten.\n\n```ts\nexport interface SchedulerJob extends Function {\n  id?: number\n}\n\nconst queue: SchedulerJob[] = []\n\nexport function queueJob(job: SchedulerJob) {\n  if (\n    !queue.length ||\n    !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)\n  ) {\n    if (job.id == null) {\n      queue.push(job)\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job)\n    }\n    queueFlush()\n  }\n}\n```\n\nAs for the job ID, in this case, we want to group them by component, so we will assign a unique identifier (UID) to each component and use them as the job IDs.  \nThe UID is simply an identifier obtained by incrementing a counter.\n\n## ReactiveEffect and Scheduler\n\nCurrently, the ReactiveEffect has the following interface (partially omitted):\n\n```ts\nclass ReactiveEffect {\n  public fn: () => T,\n\n  run() {}\n}\n```\n\nWith the implementation of the scheduler, let's make a slight change.  \nCurrently, we register a function to `fn` as an effect, but this time, let's divide it into \"actively executed effects\" and \"passively executed effects\".  \nReactive effects can be actively executed by the side that sets the effect, or they can be passively executed by being triggered by some external action after being added to a dependency (`dep`).  \nFor the latter type of effect, which is added to multiple `depsMap` and triggered by multiple sources, scheduling is necessary (on the other hand, if it is explicitly called actively, such scheduling is not necessary).\n\nLet's consider a specific example. In the `setupRenderEffect` function of the renderer, you may have the following implementation:\n\n```ts\nconst effect = (instance.effect = new ReactiveEffect(() => componentUpdateFn))\nconst update = (instance.update = () => effect.run())\nupdate()\n```\n\nThe `effect` created here, which is a `reactiveEffect`, will later be tracked by a reactive object when the `setup` function is executed. This clearly requires implementation of scheduling (because it will be triggered from various places).  \nHowever, regarding the `update()` function being called here, it should simply execute the effect, so scheduling is not necessary.  \nYou might think, \"Can't we just call `componentUpdateFn` directly then?\" But please remember the implementation of the `run` function. Simply calling `componentUpdateFn` does not set the `activeEffect`.  \nSo, let's separate the \"actively executed effects\" and the \"passively executed effects (effects that require scheduling)\".\n\nAs the final interface in this chapter, it will look like this:\n\n```ts\n// The first argument of ReactiveEffect is the actively executed effect, and the second argument is the passively executed effect\nconst effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n  queueJob(update),\n))\nconst update: SchedulerJob = (instance.update = () => effect.run())\nupdate.id = instance.uid\nupdate()\n```\n\nIn terms of implementation, in addition to `fn`, the `ReactiveEffect` will have a `scheduler` function, and in the `triggerEffect` function, the scheduler will be executed first if it exists.\n\n```ts\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null\n  );\n}\n```\n\n```ts\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler()\n  } else {\n    effect.run() // If there is no scheduler, execute the effect normally\n  }\n}\n```\n\n---\n\nNow, let's implement scheduling with queue management and the classification of effects while reading the source code!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/040_scheduler)\n\n## We want nextTick\n\nIf you have read the source code when implementing the scheduler, you may have noticed the appearance of \"nextTick\" and wondered if it is used here. First, let's talk about the task we want to achieve this time. Please take a look at this code:\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = () => {\n      state.count++\n\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nTry clicking this button and take a look at the console.\n\n![Old DOM state before nextTick](/figures/20-basic-virtual-dom/scheduler/old-state-dom.png)\n\nEven though we output to the console after updating `state.count`, the information is outdated. This is because the DOM is not instantly updated when the state is updated, and at the time of console output, the DOM is still in the old state.\n\nThis is where \"nextTick\" comes in.\n\nhttps://vuejs.org/api/general.html#nexttick\n\n\"nextTick\" is an API of the scheduler that allows you to wait until the DOM changes are applied by the scheduler. The implementation of \"nextTick\" is very simple. It just keeps the job (promise) being flushed in the scheduler and connects it to \"then\".\n\n```ts\nexport function nextTick<T = void>(\n  this: T,\n  fn?: (this: T) => void,\n): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise\n  return fn ? p.then(this ? fn.bind(this) : fn) : p\n}\n```\n\nWhen the job is completed (the promise is resolved), the callback passed to \"nextTick\" is executed. (If there is no job in the queue, it is connected to \"then\" of \"resolvedPromise\") Naturally, \"nextTick\" itself also returns a Promise, so as a developer interface, you can pass a callback or await \"nextTick\".\n\n```ts\nimport { createApp, h, reactive, nextTick } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = async () => {\n      state.count++\n\n      await nextTick() // Wait\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nNow, let's actually rewrite the implementation of the current scheduler to keep \"currentFlushPromise\" and implement \"nextTick\"!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/050_next_tick)\n"
  },
  {
    "path": "book/online-book/src/20-basic-virtual-dom/040-patch-other-attrs.md",
    "content": "# Patch for Props that cannot be handled\n\nIn this chapter, let's implement a patch for Props that cannot be handled at the moment.\nBelow are some examples of Props that need to be handled, but try to implement them by referring to the original implementation while filling in the missing parts on your own!\nBy doing so, it should become more practical!\n\nThere is nothing particularly new. It should be possible to implement it sufficiently based on what we have done so far.\n\nWhat I want to focus on is the implementation of runtime-dom/modules.\n\n## Comparison between old and new\n\nCurrently, updates can only be made based on the props of n2.\nLet's update based on n1 and n2.\n\n```ts\nconst oldProps = n1.props || {}\nconst newProps = n2.props || {}\n```\n\nProps that exist in n1 but not in n2 should be removed.\nAlso, if the values are the same even if they exist in both, there is no need to patch, so skip it.\n\n## class / style (Note)\n\nThere are multiple ways to bind class and style.\n\n```html\n<p class=\"static property\">hello</p>\n<p :class=\"'dynamic property'\">hello</p>\n<p :class=\"['dynamic', 'property', 'array']\">hello</p>\n<p :class=\"{ dynamic: true, property: true, array: true}\">hello</p>\n<p class=\"static property\" :class=\"'mixed dynamic property'\">hello</p>\n<p style=\"static: true;\" :style=\"{ mixed-dynamic: 'true' }\">hello</p>\n```\n\nTo achieve these, the concept of `transform` explained in the Basic Template Compiler section is required.\nIt can be implemented anywhere as long as it does not deviate from the design of the original Vue, but we will skip it here because we want to follow the design of the original Vue in this book.\n\n## innerHTML / textContent\n\ninnerHTML and textContent are a bit special compared to other Props.\\\nThis is because if an element with this Prop has child elements, they need to be unmounted.\n\nFor example, consider the following case:\n\n```ts\nh('div', { innerHTML: '<p>hello</p>' }, [\n  h(SomeComponent, {}, [])\n])\n```\n\nIn this case, the content of the div element will be overwritten by `innerHTML` to `<p>hello</p>`.\\\nHowever, `SomeComponent` passed as children already exists in the virtual DOM, and if it is not properly unmounted, the following problems will occur:\n\n- Event listeners will not be removed\n- Component lifecycle hooks (such as onUnmounted) will not be called\n- It can cause memory leaks\n\nTherefore, when setting innerHTML or textContent, existing child elements need to be unmounted.\n\n### Implementation\n\nFirst, extend the type definition of `patchProp` to accept `prevChildren` and `unmountChildren`.\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[], // Added\n    unmountChildren?: (children: VNode<HostNode>[]) => void, // Added\n  ): void;\n  // ...\n}\n```\n\nNext, implement the handling of innerHTML/textContent in the `patchDOMProp` function.\n\n`~/packages/runtime-dom/modules/props.ts`\n\n```ts\nexport function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === 'innerHTML' || key === 'textContent') {\n    // Unmount existing child elements if any\n    if (prevChildren) {\n      unmountChildren(prevChildren)\n    }\n    el[key] = value == null ? '' : value\n    return\n  }\n\n  // ... (handling of other props)\n}\n```\n\nThen, pass `prevChildren` and `unmountChildren` when calling `patchDOMProp` from `patchProp`.\n\n`~/packages/runtime-dom/patchProp.ts`\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === 'style') {\n    patchStyle(el, prevValue, nextValue)\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue)\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren) // Pass prevChildren, unmountChildren\n  } else {\n    patchAttr(el, key, nextValue)\n  }\n}\n```\n\nFinally, pass the appropriate arguments when calling `hostPatchProp` in renderer.ts.\n\n`~/packages/runtime-core/renderer.ts` `mountElement` and `patchElement`\n\n```ts\nconst mountElement = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children as VNode[], el, anchor)\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(\n        el,\n        key,\n        null,\n        props[key],\n        vnode.children as VNode[], // Added\n        unmountChildren, // Added\n      )\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\nNow, when innerHTML or textContent is used, existing child elements will be properly unmounted.\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/060_other_props)\n"
  },
  {
    "path": "book/online-book/src/30-basic-reactivity-system/005-reactivity-optimization.md",
    "content": "# Reactivity Optimization\n\n::: info About this chapter\nThis chapter explains the [alien-signals](https://github.com/stackblitz/alien-signals)-based reactivity system optimization that will be introduced in Vue 3.6.\\\nchibivue's implementation has also been updated based on this algorithm.\n:::\n\n## Background\n\nVue.js's Reactivity System underwent significant performance optimizations in Vue 3.4. However, Vue 3.5 switched to a pull-based algorithm similar to Preact, changing the direction of the Reactivity System.\n\nTo further research push-pull based implementations, Johnson Chu , a core contributor to Vue, developed [alien-signals](https://github.com/stackblitz/alien-signals) as an independent project.\n\nalien-signals is a signal library reimplemented based on Vue 3.4's Reactivity System, featuring:\n\n- **Lightweight**: Minimal memory footprint\n- **Fast**: Performance improvements\n- **Memory efficient**: Reduced memory usage\n\nThese achievements will be ported to Vue's core Reactivity System in Vue 3.6.\n\nReference: [vuejs/core#12349](https://github.com/vuejs/core/pull/12349)\n\n## Push-Pull Reactivity Algorithm\n\nLet's briefly explain the Push-Pull algorithm adopted by alien-signals.\n\n### Push-based vs Pull-based\n\nThere are two main approaches to reactivity systems:\n\n**Push-based**\n\nWhen a dependency changes, all dependent computed values are immediately updated.\n\n```\nsignal changes → immediately update all computeds → execute effects\n```\n\nPros: Always guarantees the latest value\nCons: Even unused computeds are updated\n\n**Pull-based**\n\nComputed values are only calculated when needed (at read time).\n\n```\nsignal changes → (do nothing) → read computed in effect → calculate at that point\n```\n\nPros: Only necessary calculations are performed\nCons: Overhead at read time\n\n### Push-Pull (Hybrid)\n\nThe Push-Pull algorithm adopted by alien-signals and Vue 3.6 combines the advantages of both:\n\n1. **Push phase**: When a signal changes, set a \"dirty\" flag on dependent computeds\n2. **Pull phase**: When a computed is read, recalculate if dirty\n\n```\nsignal changes → propagate dirty flag → read computed in effect → recalculate if dirty\n```\n\n![Push-Pull reactivity overview](/figures/30-basic-reactivity-system/reactivity-optimization/push-pull-overview.svg)\n\nThis approach provides:\n- Avoiding unnecessary calculations (advantage of Pull)\n- Efficient dependency tracking (advantage of Push)\n\n<KawaikoNote variant=\"funny\" title=\"Best of Both Worlds!\">\n\nThe Push-Pull algorithm is a clever approach that combines the best of both Push and Pull.\\\nThe strategy of \"propagate only the dirty flag when changes occur, and do the actual calculation when needed\" thoroughly eliminates unnecessary computations!\n\n</KawaikoNote>\n\n## Basic API of alien-signals\n\nalien-signals provides a very simple API:\n\n```ts\nimport { signal, computed, effect } from 'alien-signals'\n\n// signal: Create a reactive value\nconst count = signal(1)\n\n// Read value\nconsole.log(count()) // 1\n\n// Update value\ncount(2)\n\n// computed: Create a derived value\nconst double = computed(() => count() * 2)\nconsole.log(double()) // 4\n\n// effect: Register a side effect\neffect(() => {\n  console.log(`Count is: ${count()}`)\n})\n\ncount(3) // \"Count is: 3\" is printed\n```\n\nCompared to Vue's `ref` and `reactive`:\n\n| alien-signals | Vue |\n|--------------|-----|\n| `signal(value)` | `ref(value)` |\n| `signal()` for reading | `.value` for reading |\n| `signal(newValue)` for writing | `.value = newValue` for writing |\n| `computed(() => ...)` | `computed(() => ...)` |\n| `effect(() => ...)` | `watchEffect(() => ...)` |\n\n## Implementation Overview\n\n::: warning\nThis chapter does not fully port the alien-signals implementation, but explains its concepts and basic mechanisms.\\\nFor a complete understanding, please refer to the [alien-signals source code](https://github.com/stackblitz/alien-signals) or the [Vue 3.6 PR](https://github.com/vuejs/core/pull/12349).\n:::\n\n<KawaikoNote variant=\"base\" title=\"Check out Johnson's explanation!\">\n\nIf you want to learn more about the alien-signals algorithm, we recommend reading the explanation written by the author, Johnson Chu!\\\n[https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30)\n\n</KawaikoNote>\n\n### Doubly Linked List\n\nOne of the important optimizations in alien-signals is managing dependencies using a doubly linked list.\n\nThe traditional Vue implementation used Set to manage dependencies:\n\n```ts\n// Traditional implementation\nclass Dep {\n  subscribers = new Set<ReactiveEffect>()\n\n  track() {\n    if (activeEffect) {\n      this.subscribers.add(activeEffect)\n    }\n  }\n\n  trigger() {\n    this.subscribers.forEach(effect => effect.run())\n  }\n}\n```\n\nalien-signals uses linked lists:\n\n```ts\n// alien-signals style\ninterface Link {\n  dep: Dep\n  sub: Subscriber\n  prevDep: Link | undefined  // Reference to previous dep of same subscriber\n  nextDep: Link | undefined  // Reference to next dep of same subscriber\n  prevSub: Link | undefined  // Reference to previous subscriber of same dep\n  nextSub: Link | undefined  // Reference to next subscriber of same dep\n}\n```\n\nThis structure provides:\n- Reduced memory usage (avoiding Set overhead)\n- O(1) addition/removal of dependencies\n- Reduced GC pressure\n\n### Version Management\n\nAnother important optimization is dirty checking using version numbers:\n\n```ts\nlet globalVersion = 0\n\nfunction triggerRef(ref: Ref) {\n  globalVersion++\n  ref.version = globalVersion\n  // Propagate dirty to subscribers\n}\n\nfunction computedGetter(computed: ComputedRef) {\n  if (computed.globalVersion !== globalVersion) {\n    // One of the dependencies may have been updated\n    if (checkDirty(computed)) {\n      // Recalculate if actually dirty\n      computed.value = computed.getter()\n    }\n    computed.globalVersion = globalVersion\n  }\n  return computed.value\n}\n```\n\nUsing a global version provides:\n- Efficient determination of whether a computed really needs recalculation\n- Avoiding unnecessary dependency traversal\n\n## Implementation in chibivue\n\nchibivue implements the Reactivity System based on this alien-signals algorithm.\n\nMain files:\n- `packages/reactivity/dep.ts` - Dependency management\n- `packages/reactivity/effect.ts` - Effect implementation\n- `packages/reactivity/ref.ts` - Ref implementation\n- `packages/reactivity/computed.ts` - Computed implementation\n\nBasic structure:\n\n```ts\n// packages/reactivity/dep.ts\nexport interface Link {\n  dep: Dep\n  sub: Subscriber\n  version: number\n  prevDep: Link | undefined\n  nextDep: Link | undefined\n  prevSub: Link | undefined\n  nextSub: Link | undefined\n}\n\nexport class Dep {\n  version = 0\n  link: Link | undefined = undefined\n  subs: Link | undefined = undefined\n\n  track(): Link | undefined {\n    // Register activeEffect as subscriber\n  }\n\n  trigger(): void {\n    // Notify all subscribers\n  }\n}\n```\n\n```ts\n// packages/reactivity/effect.ts\nexport class ReactiveEffect<T = any> implements Subscriber {\n  deps: Link | undefined = undefined\n  depsTail: Link | undefined = undefined\n\n  run(): T {\n    // Execute effect function and collect dependencies\n  }\n}\n```\n\nThe following chapters will build on this optimized Reactivity System.\n\n<KawaikoNote variant=\"base\" title=\"Moving Forward\">\n\nDid you understand the concepts of alien-signals?\\\nLinked lists and version management might feel difficult at first, but you'll naturally understand them as you write code.\\\nLet's implement ref and computed on top of this optimized mechanism in the next chapter!\n\n</KawaikoNote>\n\n## Summary\n\n- Vue 3.6 will introduce an optimized Reactivity System based on alien-signals\n- Push-Pull algorithm enables efficient dirty checking and lazy evaluation\n- Doubly linked list for dependency management improves memory efficiency\n- Version number-based dirty checking avoids unnecessary recalculations\n\nFrom the next chapter, we will implement APIs like ref and computed on top of this optimized Reactivity System.\n\n## References\n\n- [stackblitz/alien-signals](https://github.com/stackblitz/alien-signals) - Official alien-signals repository\n- [Detailed explanation of alien-signals algorithm](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30) - Detailed explanation by the author Johnson Chu\n- [vuejs/core#12349](https://github.com/vuejs/core/pull/12349) - Vue 3.6 port PR\n- [Mastering Vue 3.6's Alien Signals](https://medium.com/@revanthkumarpatha/mastering-vue-3-6s-alien-signals-practical-examples-and-use-cases-7df02a159d8a) - Medium article\n"
  },
  {
    "path": "book/online-book/src/30-basic-reactivity-system/010-ref-api.md",
    "content": "# ref api (Basic Reactivity System Start)\n\n::: tip Prerequisites\nBefore reading this chapter, we recommend reading [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization) to understand the concepts of the alien-signals based optimized reactivity system.\n:::\n\n## Review of ref api (and implementation)\n\nVue.js has various APIs related to reactivity, and among them, ref is particularly famous.  \nEven in the official documentation, it is introduced as the first topic under the name \"Reactivity Core\".  \nhttps://vuejs.org/api/reactivity-core.html#ref\n\nSo, what is ref API?\nAccording to the official documentation,\n\n> The ref object is mutable - i.e. you can assign new values to .value. It is also reactive - i.e. any read operations to .value are tracked, and write operations will trigger associated effects.\n\n> If an object is assigned as a ref's value, the object is made deeply reactive with reactive(). This also means if the object contains nested refs, they will be deeply unwrapped.\n\n(Quote: https://vuejs.org/api/reactivity-core.html#ref)\n\nIn short, the ref object has two characteristics:\n\n- Get/set operations on the value property trigger track/trigger.\n- When an object is assigned to the value property, the value property becomes a reactive object.\n\nTo explain it in code,\n\n```ts\nconst count = ref(0)\ncount.value++ // effect (characteristic 1)\n\nconst state = ref({ count: 0 })\nstate.value = { count: 1 } // effect (characteristic 1)\nstate.value.count++ // effect (characteristic 2)\n```\n\nThat's what it means.\n\nUntil you can distinguish between ref and reactive, you may confuse the distinction between `ref(0)` and `reactive({ value: 0 })`, but considering the two characteristics mentioned above, you can see that they have completely different meanings.\nref does not generate a reactive object like `{ value: x }`. The track/trigger for get/set operations on the value is performed by the implementation of ref, and if the part corresponding to x is an object, it becomes a reactive object.\n\nIn terms of implementation, it looks like this:\n\n```ts\nclass RefImpl<T> {\n  private _value: T\n  public dep?: Dep = undefined\n\n  get value() {\n    trackRefValue(this)\n  }\n\n  set value(newVal) {\n    this._value = toReactive(newVal)\n    triggerRefValue(this)\n  }\n}\n\nconst toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value\n```\n\nLet's implement ref while looking at the source code!\nThere are various functions and classes, but for now, it would be sufficient to focus on the RefImpl class and the ref function.\n\nOnce you can run the following source code, it's OK!\n(Note: The template compiler needs to support ref separately, so it won't work)\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['Increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/010_ref)\n\n## shallowRef\n\nNow, let's continue implementing more ref-related APIs.  \nAs mentioned earlier, one of the characteristics of ref is that \"when an object is assigned to the value property, the value property becomes a reactive object\". shallowRef does not have this characteristic.\n\n> Unlike ref(), the inner value of a shallow ref is stored and exposed as-is, and will not be made deeply reactive. Only the .value access is reactive.\n\n(Quote: https://vuejs.org/api/reactivity-advanced.html#shallowref)\n\nThe task is very simple. We can use the implementation of RefImpl as it is and skip the part of `toReactive`.  \nLet's implement it while reading the source code!\n\nOnce you can run the following source code, it's OK!\n\n```ts\nimport { createApp, h, shallowRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // Clicking does not trigger re-rendering\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n### triggerRef\n\nAs mentioned earlier, since the value of shallow ref is not a reactive object, changes to it will not trigger effects.  \nHowever, the value itself is an object, so it has been changed.  \nTherefore, there is an API to forcefully trigger effects. It is triggerRef.\n\nhttps://vuejs.org/api/reactivity-advanced.html#triggerref\n\n```ts\nimport { createApp, h, shallowRef, triggerRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n    const forceUpdate = () => {\n      triggerRef(state)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // Clicking does not trigger re-rendering\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n\n        h(\n          'button', // The rendering is updated to the value currently held by state.value.count\n          { onClick: forceUpdate },\n          ['force update !'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/020_shallow_ref)\n\n## toRef\n\ntoRef is an API that generates a ref to a property of a reactive object.\n\nhttps://vuejs.org/api/reactivity-utilities.html#toref\n\nIt is often used to convert specific properties of props into refs.\n\n```ts\nconst count = toRef(props, 'count')\nconsole.log(count.value)\n```\n\nThe ref created by toRef is synchronized with the original reactive object.\nIf you make changes to this ref, the original reactive object will also be updated, and if there are any changes to the original reactive object, this ref will also be updated.\n\n```ts\nimport { createApp, h, reactive, toRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const stateCountRef = toRef(state, 'count')\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`state.count: ${state.count}`]),\n        h('p', {}, [`stateCountRef.value: ${stateCountRef.value}`]),\n        h('button', { onClick: () => state.count++ }, ['updateState']),\n        h('button', { onClick: () => stateCountRef.value++ }, ['updateRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nLet's implement while reading the source code!\n\n※ From v3.3, the normalization feature has been added to toRef. chibivue does not implement this feature.  \nPlease check the signature in the official documentation for more details! (https://vuejs.org/api/reactivity-utilities.html#toref)\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/030_to_ref)\n\n## toRefs\n\nGenerates refs for all properties of a reactive object.\n\nhttps://vuejs.org/api/reactivity-utilities.html#torefs\n\n```ts\nimport { createApp, h, reactive, toRefs } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ foo: 1, bar: 2 })\n    const stateAsRefs = toRefs(state)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`[state]: foo: ${state.foo}, bar: ${state.bar}`]),\n        h('p', {}, [\n          `[stateAsRefs]: foo: ${stateAsRefs.foo.value}, bar: ${stateAsRefs.bar.value}`,\n        ]),\n        h('button', { onClick: () => state.foo++ }, ['update state.foo']),\n        h('button', { onClick: () => stateAsRefs.bar.value++ }, [\n          'update stateAsRefs.bar.value',\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nThis can be easily implemented using the implementation of toRef.\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/040_to_refs)\n"
  },
  {
    "path": "book/online-book/src/30-basic-reactivity-system/020-computed-watch.md",
    "content": "# computed / watch api\n\n::: warning\nThe implementation explained here is based on the version prior to the currently drafted [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization).  \\\nOnce [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization) is completed, the content of this chapter will be updated to align with it.\n:::\n\n## Review of computed (and implementation)\n\nIn the previous chapter, we implemented the ref-related APIs. Next, let's talk about computed.\nhttps://vuejs.org/api/reactivity-core.html#computed\n\nComputed has two signatures: read-only and writable.\n\n```ts\n// read-only\nfunction computed<T>(\n  getter: () => T,\n  // see \"Computed Debugging\" link below\n  debuggerOptions?: DebuggerOptions,\n): Readonly<Ref<Readonly<T>>>\n\n// writable\nfunction computed<T>(\n  options: {\n    get: () => T\n    set: (value: T) => void\n  },\n  debuggerOptions?: DebuggerOptions,\n): Ref<T>\n```\n\nThe official implementation is a bit complex, but let's start with a simple structure.\n\nThe simplest way to implement it is to trigger the callback every time the value is retrieved.\n\n```ts\nexport class ComputedRefImpl<T> {\n  constructor(private getter: ComputedGetter<T>) {}\n\n  get value() {\n    return this.getter()\n  }\n\n  set value() {}\n}\n```\n\nHowever, this is not really computed. It's just calling a function (which is not very exciting).\n\nIn reality, we want to track dependencies and recalculate when the value changes.\n\nTo achieve this, we use a mechanism where we update the `_dirty` flag as a scheduler job.\nThe `_dirty` flag is a flag that represents whether the value needs to be recalculated or not. It is updated when triggered by a dependency.\n\nHere's an example of how it works:\n\n```ts\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined\n  private _value!: T\n  public readonly effect: ReactiveEffect<T>\n  public _dirty = true\n\n  constructor(getter: ComputedGetter<T>) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true\n      }\n    })\n  }\n\n  get value() {\n    trackRefValue(this)\n    if (this._dirty) {\n      this._dirty = false\n      this._value = this.effect.run()\n    }\n    return this._value\n  }\n}\n```\n\nComputed actually has a lazy evaluation nature, so the value is only recalculated when it is read for the first time.\nWe update this flag to true and the function is triggered by multiple dependencies, so we register it as the scheduler of ReactiveEffect.\n\nThis is the basic flow. When implementing, there are a few points to note, so let's summarize them below.\n\n- When updating the `_dirty` flag to true, trigger the dependencies it has.\n  ```ts\n  if (!this._dirty) {\n    this._dirty = true\n    triggerRefValue(this)\n  }\n  ```\n- Since computed is classified as `ref`, mark `__v_isRef` as true.\n- If you want to implement a setter, implement it last. First, aim to make it computable.\n\nNow that we are ready, let's implement it! If the code below works as expected, it's OK! (Please make sure that only the computed dependencies are triggered!)\n\n```ts\nimport { computed, createApp, h, reactive, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = reactive({ value: 0 })\n    const count2 = reactive({ value: 0 })\n    const double = computed(() => {\n      console.log('computed')\n      return count.value * 2\n    })\n    const doubleDouble = computed(() => {\n      console.log('computed (doubleDouble)')\n      return double.value * 2\n    })\n\n    const countRef = ref(0)\n    const doubleCountRef = computed(() => {\n      console.log('computed (doubleCountRef)')\n      return countRef.value * 2\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('p', {}, [`count2: ${count2.value}`]),\n        h('p', {}, [`double: ${double.value}`]),\n        h('p', {}, [`doubleDouble: ${doubleDouble.value}`]),\n        h('p', {}, [`doubleCountRef: ${doubleCountRef.value}`]),\n        h('button', { onClick: () => count.value++ }, ['update count']),\n        h('button', { onClick: () => count2.value++ }, ['update count2']),\n        h('button', { onClick: () => countRef.value++ }, ['update countRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/050_computed)\n(with setter):\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/060_computed_setter)\n\n## Implementation of Watch\n\nhttps://vuejs.org/api/reactivity-core.html#watch\n\nThere are various forms of the watch API. Let's start by implementing the simplest form, which watches using a getter function.\nFirst, let's aim for the code below to work.\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    watch(\n      () => state.count,\n      () => alert('state.count was changed!'),\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: () => state.count++ }, ['update state']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nThe implementation of watch is not in reactivity, but in runtime-core (apiWatch.ts).\n\nIt may look a bit complex because there are various APIs mixed together, but it's actually quite simple if you narrow down the scope.\nI have already implemented the signature of the target API (watch function) below, so please try implementing it. I believe you can do it if you have acquired the knowledge of reactivity so far!\n\n```ts\nexport type WatchEffect = (onCleanup: OnCleanup) => void\n\nexport type WatchSource<T = any> = () => T\n\ntype OnCleanup = (cleanupFn: () => void) => void\n\nexport function watch<T>(\n  source: WatchSource<T>,\n  cb: (newValue: T, oldValue: T) => void,\n) {\n  // TODO:\n}\n```\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/070_watch)\n\n## Other APIs of watch\n\nOnce you have the base, it's just a matter of extending it. There is no need for further explanation.\n\n- Watching ref\n  ```ts\n  const count = ref(0)\n  watch(count, () => {\n    /** some effects */\n  })\n  ```\n- Watching multiple sources\n\n  ```ts\n  const count = ref(0)\n  const count2 = ref(0)\n  const count3 = ref(0)\n  watch([count, count2, count3], () => {\n    /** some effects */\n  })\n  ```\n\n- Immediate\n\n  ```ts\n  const count = ref(0)\n  watch(\n    count,\n    () => {\n      /** some effects */\n    },\n    { immediate: true },\n  )\n  ```\n\n- Deep\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(\n    () => state,\n    () => {\n      /** some effects */\n    },\n    { deep: true },\n  )\n  ```\n\n- Reactive object\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(state, () => {\n    /** some effects */\n  }) // automatically in deep mode\n  ```\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/080_watch_api_extends)\n\n## watchEffect\n\nhttps://vuejs.org/api/reactivity-core.html#watcheffect\n\nImplementing watchEffect is easy using the watch implementation.\n\n```ts\nconst count = ref(0)\n\nwatchEffect(() => console.log(count.value))\n// -> logs 0\n\ncount.value++\n// -> logs 1\n```\n\nYou can implement it like immediate for the image.\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/090_watch_effect)\n\n---\n\n※ Cleanup will be done in a separate chapter.\n"
  },
  {
    "path": "book/online-book/src/30-basic-reactivity-system/030-reactive-proxy-handlers.md",
    "content": "# Various Reactive Proxy Handlers\n\n::: warning\nThe implementation explained here is based on the version prior to the currently drafted [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization).  \\\nOnce [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization) is completed, the content of this chapter will be updated to align with it.\n:::\n\n## Objects that should not be reactive\n\nNow, let's solve a problem with the current Reactivity System.  \nFirst, try running the following code.\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nIf you check the console, you should see the following result:\n\n![Reactive HTML element console output](/figures/30-basic-reactivity-system/reactive-proxy-handlers/reactive-html-element.png)\n\nNow, let's add a focus function.\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSurprisingly, it throws an error.\n\n![Focus result in a reactive HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-reactive-html-element.png)\n\nThe reason for this is that the element obtained by `document.getElementById` is used to generate a Proxy itself.\n\nWhen a Proxy is generated, the value becomes the Proxy instead of the original object, causing the loss of HTML element functionality.\n\n## Determine the object before generating a reactive Proxy\n\nThe determination method is very simple. Use `Object.prototype.toString`.\nLet's see how `Object.prototype.toString` determines an HTMLInputElement in the code above.\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value?.toString())\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n![HTMLElement toString result](/figures/30-basic-reactivity-system/reactive-proxy-handlers/element-to-string.png)\n\nThis allows us to determine the type of the object. Although it is somewhat hard-coded, let's generalize this determination function.\n\n```ts\n// shared/general.ts\nexport const objectToString = Object.prototype.toString // already used in isMap and isSet\nexport const toTypeString = (value: unknown): string =>\n  objectToString.call(value)\n\n// Function to be added this time\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1)\n}\n```\n\nThe reason for using `slice` is to obtain the string corresponding to `hoge` in `[Object hoge]`.\n\nThen, let's determine the type of the object by using `reactive toRawType` and branch it.\nSkip generating a Proxy for HTMLInput.\n\nIn reactive.ts, get the rawType and determine the type of the object that will be the target of reactive.\n\n```ts\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value)\n    ? TargetType.INVALID\n    : targetTypeMap(toRawType(value))\n}\n```\n\n```ts\nexport function reactive<T extends object>(target: T): T {\n  const targetType = getTargetType(target)\n  if (targetType === TargetType.INVALID) {\n    return target\n  }\n\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\nNow, the focus code should work!\n\n![Focus result in a plain HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-element.png)\n\n## Implementing TemplateRefs\n\nNow that we can put HTML elements into Ref, let's implement TemplateRef.\n\nRef can be used to reference a template by using the ref attribute.\n\nhttps://vuejs.org/guide/essentials/template-refs.html\n\nThe goal is to make the following code work:\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { ref: inputRef }, []),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nIf you've come this far, you probably already see how to implement it.\nYes, just add ref to VNode and inject the value during rendering.\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  key: string | number | symbol | null\n  ref: Ref | null // This\n  // .\n  // .\n}\n```\n\nIn the original implementation, it is called `setRef`. Find it, read it, and implement it!\nIn the original implementation, it is more complicated, with ref being an array and accessible with `$ref`, but for now, let's aim for a code that works with the above code.\n\nBy the way, if it is a component, assign the component's `setupContext` to the ref.  \n(Note: In reality, you should pass the component's proxy, but it is not yet implemented, so we are using `setupContext` for now.)\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst Child = {\n  setup() {\n    const action = () => alert('clicked!')\n    return { action }\n  },\n\n  template: `<button @click=\"action\">action (child)</button>`,\n}\n\nconst app = createApp({\n  setup() {\n    const childRef = ref<any>(null)\n    const childAction = () => {\n      childRef.value?.action()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('div', {}, [\n          h(Child, { ref: childRef }, []),\n          h('button', { onClick: childAction }, ['action (parent)']),\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/110_template_refs)\n\n## Handling Objects with Changing Keys\n\nActually, the current implementation cannot handle objects with changing keys.\nThis includes arrays as well.\nIn other words, the following components do not work correctly:\n\n```ts\nconst App = {\n  setup() {\n    const array = ref<number[]>([])\n    const mutateArray = () => {\n      array.value.push(Date.now()) // No effect is triggered even when this is called (the key for set is \"0\")\n    }\n\n    const record = reactive<Record<string, number>>({})\n    const mutateRecord = () => {\n      record[Date.now().toString()] = Date.now() // No effect is triggered even when the key is changed\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`array: ${JSON.stringify(array.value)}`]),\n        h('button', { onClick: mutateArray }, ['update array']),\n\n        h('p', {}, [`record: ${JSON.stringify(record)}`]),\n        h('button', { onClick: mutateRecord }, ['update record']),\n      ])\n  },\n}\n```\n\nHow can we solve this?\n\n### For Arrays\n\nArrays are essentially objects, so when a new element is added, its index is passed as the key to the `set` handler of the Proxy.\n\n```ts\nconst p = new Proxy([], {\n  set(target, key, value, receiver) {\n    console.log(key) // ※\n    Reflect.set(target, key, value, receiver)\n    return true\n  },\n})\n\np.push(42) // 0\n```\n\nHowever, we cannot track each of these keys individually.\nTherefore, we can track the `length` of the array to trigger changes in the array.\n\nIt is worth noting that the `length` is already being tracked.\n\nIf you execute the following code in a browser or similar environment, you will see that `length` is called when the array is stringified using `JSON.stringify`.\n\n```ts\nconst data = new Proxy([], {\n  get(target, key) {\n    console.log('get!', key)\n    return Reflect.get(target, key)\n  },\n})\n\nJSON.stringify(data)\n// get! length\n// get! toJSON\n```\n\nIn other words, the `length` already has an effect registered. So, all we need to do is extract this effect and trigger it when an index is set.\n\nIf the key is determined to be an index, we trigger the effect of `length`.\nOf course, there may be other dependencies, so we extract them into an array called `deps` and trigger the effects together.\n\n```ts\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  // This\n  if (isIntegerKey(key)) {\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n```ts\n// shared/general.ts\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) &&\n  key !== 'NaN' &&\n  key[0] !== '-' &&\n  '' + parseInt(key, 10) === key\n```\n\nNow, arrays should work correctly.\n\n### For Objects (Records)\n\nNext, let's consider objects. Unlike arrays, objects do not have the `length` property.\n\nWe can make a small modification here.\nWe can prepare a symbol called `ITERATE_KEY` and use it in a similar way to the `length` property for arrays.\nYou may not understand what I mean, but since `depsMap` is just a Map, there is no problem using a symbol that we define as a key.\n\nThe order of operations is slightly different from arrays, but let's start by considering the `trigger` function.\nWe can implement it as if there is a `ITERATE_KEY` with registered effects.\n\n```ts\nexport const ITERATE_KEY = Symbol()\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  if (!isArray(target)) {\n    // If it is not an array, trigger the effect registered with ITERATE_KEY\n    deps.push(depsMap.get(ITERATE_KEY))\n  } else if (isIntegerKey(key)) {\n    // New index added to array -> length changes\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\nThe problem is how to track effects for `ITERATE_KEY`.\n\nHere, we can use the `ownKeys` Proxy handler.\n\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys\n\n`ownKeys` is called by functions like `Object.keys()` or `Reflect.ownKeys()`, but it is also called by `JSON.stringify`.\n\nYou can confirm this by running the following code in a browser or similar environment:\n\n```ts\nconst data = new Proxy(\n  {},\n  {\n    get(target, key) {\n      return Reflect.get(target, key)\n    },\n    ownKeys(target) {\n      console.log('ownKeys!!!')\n      return Reflect.ownKeys(target)\n    },\n  },\n)\n\nJSON.stringify(data)\n```\n\nWe can use this to track `ITERATE_KEY`.\nFor arrays, we don't need it, so we can simply track the `length`.\n\n```ts\nexport const mutableHandlers: ProxyHandler<object> = {\n  // .\n  // .\n  ownKeys(target) {\n    track(target, isArray(target) ? 'length' : ITERATE_KEY)\n    return Reflect.ownKeys(target)\n  },\n}\n```\n\nNow, we should be able to handle objects with changing keys!\n\n## Support for Collection-based built-in objects\n\nCurrently, when looking at the implementation of reactive.ts, it only targets Object and Array.\n\n```ts\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n```\n\nIn Vue.js, in addition to these, it also supports Map, Set, WeakMap, and WeakSet.\n\nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/reactive.ts#L43C1-L56C2\n\nAnd these objects are implemented as separate Proxy handlers. It is called `collectionHandlers`.\n\nHere, we will implement this `collectionHandlers` and aim for the following code to work.\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ map: new Map(), set: new Set() })\n\n    return () =>\n      h('div', {}, [\n        h('h1', {}, [`ReactiveCollection`]),\n\n        h('p', {}, [\n          `map (${state.map.size}): ${JSON.stringify([...state.map])}`,\n        ]),\n        h('button', { onClick: () => state.map.set(Date.now(), 'item') }, [\n          'update map',\n        ]),\n\n        h('p', {}, [\n          `set (${state.set.size}): ${JSON.stringify([...state.set])}`,\n        ]),\n        h('button', { onClick: () => state.set.add('item') }, ['update set']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nIn `collectionHandlers`, we implement handlers for methods such as add, set, and delete.  \nThe implementation of these can be found in `collectionHandlers.ts`.  \nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/collectionHandlers.ts#L0-L1  \nBy determining the `TargetType`, if it is a collection type, we generate a Proxy based on this handler for `h`.  \nLet's actually implement it!\n\nOne thing to note is that when passing the target itself to the receiver of Reflect, it may cause an infinite loop if the target itself has a Proxy set.  \nTo avoid this, we change the structure to have the raw data attached to the target, and when implementing the Proxy handler, we modify it to operate on this raw data.\n\n```ts\nexport const enum ReactiveFlags {\n  RAW = '__v_raw',\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any\n}\n```\n\nStrictly speaking, this implementation should have been done for the normal reactive handler as well, but it was omitted to minimize unnecessary explanations and because there were no problems so far.  \nLet's try implementing it so that if the key that enters the getter is `ReactiveFlags.RAW`, it returns the raw data instead of a Proxy.\n\nAlong with this, we also implement a function called `toRaw` that recursively retrieves raw data from the target and ultimately obtains data that is in a raw state.\n\n```ts\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW]\n  return raw ? toRaw(raw) : observed\n}\n```\n\nBy the way, this `toRaw` function is also provided as an API function.\n\nhttps://vuejs.org/api/reactivity-advanced.html#toraw\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/120_proxy_handler_improvement)\n"
  },
  {
    "path": "book/online-book/src/30-basic-reactivity-system/040-effect-scope.md",
    "content": "# Effect Cleanup and Effect Scope\n\n::: warning\nThe implementation explained here is based on the version prior to the currently drafted [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization).  \\\nOnce [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization) is completed, the content of this chapter will be updated to align with it.\n:::\n\n## Cleanup of ReactiveEffect\n\nWe haven't been cleaning up the effects we registered so far. Let's add cleanup processing to ReactiveEffect.\n\nImplement a method called `stop` in ReactiveEffect.  \nAdd a flag to ReactiveEffect to indicate whether it is active or not, and in the `stop` method, switch it to `false` while removing the dependencies.\n\n```ts\nexport class ReactiveEffect<T = any> {\n  active = true // Added\n  //.\n  //.\n  //.\n  stop() {\n    if (this.active) {\n      this.active = false\n    }\n  }\n}\n```\n\nWith this basic implementation, all we need to do is remove all the dependencies when the `stop` method is executed.  \nAdditionally, let's add an implementation of hooks that allows us to register the processing we want to perform during cleanup, and handling when `activeEffect` is itself.\n\n```ts\nexport class ReactiveEffect<T = any> {\n  private deferStop?: boolean // Added\n  onStop?: () => void // Added\n  parent: ReactiveEffect | undefined = undefined // Added (to be referenced in finally)\n\n  run() {\n    if (!this.active) {\n      return this.fn() // If active is false, simply execute the function\n    }\n\n    try {\n      this.parent = activeEffect\n      activeEffect = this\n      const res = this.fn()\n      return res\n    } finally {\n      activeEffect = this.parent\n      this.parent = undefined\n      if (this.deferStop) {\n        this.stop()\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      // If activeEffect is itself, set a flag to stop after run is finished\n      this.deferStop = true\n    } else if (this.active) {\n      // ...\n      if (this.onStop) {\n        this.onStop() // Execute registered hooks\n      }\n      // ...\n    }\n  }\n}\n```\n\nNow that we have added cleanup processing to ReactiveEffect, let's also implement the cleanup function for watch.\n\nIf the following code works, it's OK.\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    const unwatch = watch(\n      () => state.count,\n      (newValue, oldValue, cleanup) => {\n        alert(`New value: ${newValue}, old value: ${oldValue}`)\n        cleanup(() => alert('Clean Up!'))\n      },\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, [`increment`]),\n        h('button', { onClick: unwatch }, [`unwatch`]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/130_cleanup_effects)\n\n## What is Effect Scope\n\nNow that we can clean up effects, we want to clean up unnecessary effects when a component is unmounted. However, it is a bit cumbersome to collect a large number of effects, whether it's watch or computed. If we try to implement it straightforwardly, it will look like this:\n\n```ts\nlet disposables = []\n\nconst counter = ref(0)\n\nconst doubled = computed(() => counter.value * 2)\ndisposables.push(() => stop(doubled.effect))\n\nconst stopWatch = watchEffect(() => console.log(`counter: ${counter.value}`))\ndisposables.push(stopWatch)\n```\n\n```ts\n// cleanup effects\ndisposables.forEach(f => f())\ndisposables = []\n```\n\nThis kind of management is cumbersome and prone to mistakes.\n\nTherefore, Vue has a mechanism called EffectScope.  \nhttps://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md\n\nThe idea is to have one EffectScope per instance, and specifically, it has the following interface:\n\n```ts\nconst scope = effectScope()\n\nscope.run(() => {\n  const doubled = computed(() => counter.value * 2)\n\n  watch(doubled, () => console.log(doubled.value))\n\n  watchEffect(() => console.log('Count: ', doubled.value))\n})\n\n// to dispose all effects in the scope\nscope.stop()\n```\n\nQuoted from: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md#basic-example\n\nAnd this EffectScope is also exposed as a user-facing API.  \nhttps://vuejs.org/api/reactivity-advanced.html#effectscope\n\n## Implementation of EffectScope\n\nAs mentioned earlier, we will have one EffectScope per instance.\n\n```ts\nexport interface ComponentInternalInstance {\n  scope: EffectScope\n}\n```\n\nAnd when the component is unmounted, we stop the collected effects.\n\n```ts\nconst unmountComponent = (...) => {\n  // .\n  // .\n  const { scope } = instance;\n  scope.stop();\n  // .\n  // .\n}\n```\n\nThe structure of EffectScope is as follows: it has a variable called `activeEffectScope` that points to the currently active EffectScope, and it manages its state with the `on/off/run/stop` methods implemented in EffectScope.  \nThe `on/off` methods lift themselves as `activeEffectScope` or restore the lifted state (return to the original EffectScope).  \nAnd when a ReactiveEffect is created, it is registered in `activeEffectScope`.\n\nSince it may be a little difficult to understand, if we write the image in source code,\n\n```ts\ninstance.scope.on()\n\n/** Some ReactiveEffect such as computed or watch is created */\nsetup()\n\ninstance.scope.off()\n```\n\nWith this, we can collect the generated effects in the EffectScope of the instance.  \nThen, when the `stop` method of this effect is triggered, we can clean up all the effects.\n\nYou should have understood the basic principles, so let's try implementing it while reading the source code!\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/140_effect_scope)\n"
  },
  {
    "path": "book/online-book/src/30-basic-reactivity-system/050-other-apis.md",
    "content": "# Other Reactivity APIs\n\n::: warning\nThe implementation explained here is based on the version prior to the currently drafted [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization).  \\\nOnce [Reactivity Optimization](/30-basic-reactivity-system/005-reactivity-optimization) is completed, the content of this chapter will be updated to align with it.\n:::\n\n## Let's implement other reactivity APIs!\n\n- customRef\n- readonly\n- shallowReactive\n- unref\n- isProxy\n- isReactive\n- isReadonly\n\nIf you have come this far, you should be able to implement them by reading the source code without any explanations!\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/150_other_apis)\n"
  },
  {
    "path": "book/online-book/src/40-basic-component-system/010-lifecycle-hooks.md",
    "content": "# Lifecycle Hooks (Basic Component System Start)\n\n## Let's Implement Lifecycle Hooks\n\nImplementing lifecycle hooks is very simple.\nYou just need to register functions in ComponentInternalInstance and execute them at the specified timing during rendering.\nThe API itself will be implemented in runtime-core/apiLifecycle.ts.\n\nOne thing to note is that you need to consider scheduling for onMounted/onUnmounted/onUpdated.\nThe registered functions should be executed after the mount, unmount, and update are completely finished.\n\nTherefore, we will implement a new type of queue called \"post\" in the scheduler. This is something that will be flushed after the existing queue flush is finished.\nImage ↓\n\n```ts\nconst queue: SchedulerJob[] = [] // Existing implementation\nconst pendingPostFlushCbs: SchedulerJob[] = [] // New queue to be created this time\n\nfunction queueFlush() {\n  queue.forEach(job => job())\n  flushPostFlushCbs() // Flush after the queue flush\n}\n```\n\nAlso, with this, let's implement an API that enqueues into pendingPostFlushCbs.\nAnd let's use it to enqueue the effect in the renderer to pendingPostFlushCbs.\n\nLifecycle hooks to be supported this time:\n\n- onMounted\n- onUpdated\n- onUnmounted\n- onBeforeMount\n- onBeforeUpdate\n- onBeforeUnmount\n\nLet's implement it aiming for the following code to work!\n\n```ts\nimport {\n  createApp,\n  h,\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  ref,\n} from 'chibivue'\n\nconst Child = {\n  setup() {\n    const count = ref(0)\n    onBeforeMount(() => {\n      console.log('onBeforeMount')\n    })\n\n    onUnmounted(() => {\n      console.log('onUnmounted')\n    })\n\n    onBeforeUnmount(() => {\n      console.log('onBeforeUnmount')\n    })\n\n    onBeforeUpdate(() => {\n      console.log('onBeforeUpdate')\n    })\n\n    onUpdated(() => {\n      console.log('onUpdated')\n    })\n\n    onMounted(() => {\n      console.log('onMounted')\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const mountFlag = ref(true)\n\n    return () =>\n      h('div', {}, [\n        h('button', { onClick: () => (mountFlag.value = !mountFlag.value) }, [\n          'toggle',\n        ]),\n        mountFlag.value ? h(Child, {}, []) : h('p', {}, ['unmounted']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/010_lifecycle_hooks)\n"
  },
  {
    "path": "book/online-book/src/40-basic-component-system/020-provide-inject.md",
    "content": "# Implementation of Provide/Inject\n\n## Let's implement Provide/Inject\n\nThis is the implementation of Provide and Inject. The implementation is quite simple as well.  \nThe basic concept is to have a place in ComponentInternalInstance to store the provided data (provides) and to keep the instance of the parent component to inherit the data.\n\nOne thing to note is that there are two entry points for provide. One is during the setup of the component, which is easy to imagine,  \nand the other is when calling provide on the App.\n\n```ts\nconst app = createApp({\n  setup() {\n    //.\n    //.\n    //.\n    provide('key', someValue) // This is a case where provide is called from the component\n    //.\n    //.\n  },\n})\n\napp.provide('key2', someValue2) // Provide on the App\n```\n\nNow, where should we store what was provided by app? Since app is not a component, it's a problem.\n\nTo give you the answer, let's say that the app instance has an object called AppContext and we will store the provides object in it.\n\nIn the future, we will add global component and custom directive settings to this AppContext.\n\nNow that we have explained everything so far, let's implement the code so that it works as follows!\n\n※ Assumed signatures\n\n```ts\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n)\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T\n```\n\n```ts\nconst Child = {\n  setup() {\n    const rootState = inject<{ count: number }>('RootState')\n    const logger = inject(LoggerKey)\n\n    const action = () => {\n      rootState && rootState.count++\n      logger?.('Hello from Child.')\n    }\n\n    return () => h('button', { onClick: action }, ['action'])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 1 })\n    provide('RootState', state)\n\n    return () =>\n      h('div', {}, [h('p', {}, [`${state.count}`]), h(Child, {}, [])])\n  },\n})\n\ntype Logger = (...args: any) => void\nconst LoggerKey = Symbol() as InjectionKey<Logger>\n\napp.provide(LoggerKey, window.console.log)\n```\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/020_provide_inject)\n"
  },
  {
    "path": "book/online-book/src/40-basic-component-system/030-component-proxy-setup-context.md",
    "content": "# Proxy and setupContext of Components\n\n## Proxy of Components\n\nOne important concept that components have is called Proxy.  \nIn simple terms, it is a Proxy that allows access to the data (public properties) of the component instance.\nThe Proxy combines the results of setup (state, functions), data, props, and other accesses.\n\nLet's consider the following code (including parts that are not implemented in chibivue, so please think of it as regular Vue):\n\n```vue\n<script>\nexport default defineComponent({\n  props: { parentCount: { type: Number, default: 0 } },\n  data() {\n    return { dataState: { count: 0 } }\n  },\n  methods: {\n    incrementData() {\n      this.dataState.count++\n    },\n  },\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return { state, increment }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <p>count (parent): {{ parentCount }}</p>\n\n    <br />\n\n    <p>count (data): {{ dataState.count }}</p>\n    <button @click=\"incrementData\">increment (data)</button>\n\n    <br />\n\n    <p>count: {{ state.count }}</p>\n    <button @click=\"increment\">increment</button>\n  </div>\n</template>\n```\n\nThis code works correctly, but how is it bound to the template?\n\nLet's consider another example.\n\n```vue\n<script setup>\nconst ChildRef = ref()\n\n// Access to methods and data of the component\n// ChildRef.value?.incrementData\n// ChildRef.value?.increment\n</script>\n\n<template>\n  <!-- Child is the component mentioned earlier -->\n  <Child :ref=\"ChildRef\" />\n</template>\n```\n\nIn this case, you can access the component's information through ref.\n\nTo achieve this, the ComponentInternalInstance has a property called proxy, which holds the Proxy for data access.\n\nIn other words, the template (render function) and ref refer to instance.proxy.\n\n```ts\ninterface ComponentInternalInstance {\n  proxy: ComponentPublicInstance | null\n}\n```\n\nThe implementation of this proxy is done using Proxy, and it is roughly as follows:\n\n```ts\ninstance.proxy = instance.proxy = new Proxy(\n  instance,\n  PublicInstanceProxyHandlers,\n)\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get(instance: ComponentRenderContext, key: string) {\n    const { setupState, ctx, props } = instance\n\n    // Check setupState -> props -> ctx in order based on the key and return the value if it exists\n  },\n}\n```\n\nLet's implement this Proxy!\n\nOnce implemented, let's modify the code to pass this proxy to the render function and ref.\n\nSource code so far:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/030_component_proxy)\n\n※ By the way, I have also implemented the implementation of defineComponent and related type checking (this allows us to infer the type of proxy data).\n\n![Component type inference result](/figures/40-basic-component-system/component-proxy-setup-context/infer-component-types.png)\n\n## setupContext\n\nhttps://ja.vuejs.org/api/composition-api-setup.html#setup-context\n\nVue has a concept called setupContext. This is the context exposed in the setup function, which includes emit and expose.\n\nAt the moment, emit is working, but it is implemented somewhat roughly.\n\n```ts\nconst setupResult = component.setup(instance.props, {\n  emit: instance.emit,\n})\n```\n\nLet's define the SetupContext interface properly and represent it as an object that the instance holds.\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupContext: SetupContext | null // Added\n}\n\nexport type SetupContext = {\n  emit: (e: string, ...args: any[]) => void\n}\n```\n\nThen, when creating an instance, generate the setupContext and pass this object as the second argument when executing the setup function.\n\n## expose\n\nOnce you've reached this point, let's try implementing SetupContext other than emit.  \nAs an example this time, let's implement expose.\n\nexpose is a function that allows you to explicitly define public properties.  \nLet's aim for a developer interface like the following:\n\n```ts\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const log = () => {\n      console.log(\n        child.value.count,\n        child.value.count2, // cannot access\n        child2.value.count,\n        child2.value.count2,\n      )\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: log }, ['log']),\n      ])\n  },\n})\n```\n\nFor components that do not use expose, everything is still public by default.\n\nAs a direction, let's have an object called `exposed` inside the instance, and if a value is set here, we will pass this object to ref for templateRef.\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  exposed: Record<string, any> | null // added\n}\n```\n\nLet's implement the expose function so that objects can be registered here.\n\n## ProxyRefs\n\nIn this chapter, we have implemented proxy and exposedProxy, but there are actually some differences from the original Vue.  \nThat is, \"ref is unwrapped\". (In the case of proxy, setupState has this property rather than proxy.)\n\nThese are implemented with ProxyRefs, and the handler is implemented under the name `shallowUnwrapHandlers`.  \nThis allows us to eliminate the redundancy of ref-specific values when writing templates or dealing with proxies.\n\n```ts\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key]\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value\n      return true\n    } else {\n      return Reflect.set(target, key, value, receiver)\n    }\n  },\n}\n```\n\n```vue\n<template>\n  <!-- <p>{{ count.value }}</p>  There is no need to write like this -->\n  <p>{{ count }}</p>\n</template>\n```\n\nIf you implement it up to this point, the following code should work.\n\n```ts\nimport { createApp, defineComponent, h, ref } from 'chibivue'\n\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>child {{ count }} {{ count2 }}</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>child2 {{ count }} {{ count2 }}</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const increment = () => {\n      child.value.count++\n      child.value.count2++ // cannot access\n      child2.value.count++\n      child2.value.count2++\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n## Template Binding and with Statement\n\nActually, there is a problem with the changes in this chapter.  \nLet's try running the following code:\n\n```ts\nconst Child2 = {\n  setup() {\n    const state = reactive({ count: 0 })\n    return { state }\n  },\n  template: `<p>child2 count: {{ state.count }}</p>`,\n}\n```\n\nIt's just a simple code, but it doesn't work.  \nIt complains that state is not defined.\n\n![state is not defined runtime error](/figures/40-basic-component-system/component-proxy-setup-context/state-is-not-defined.png)\n\nThe reason for this is that when passing a Proxy as an argument to the with statement, has must be defined.\n\n[Creating dynamic namespaces using the with statement and a proxy (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with#creating_dynamic_namespaces_using_the_with_statement_and_a_proxy)\n\nSo let's implement has in PublicInstanceProxyHandlers.  \nIf the key exists in setupState, props, or ctx, it should return true.\n\n```ts\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  // .\n  // .\n  // .\n  has(\n    { _: { setupState, ctx, propsOptions } }: ComponentRenderContext,\n    key: string,\n  ) {\n    let normalizedProps\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    )\n  },\n}\n```\n\nIf it works correctly, it should work fine!\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/040_setup_context)\n"
  },
  {
    "path": "book/online-book/src/40-basic-component-system/040-component-slot.md",
    "content": "# Slots\n\n## Implementation of Default Slot\n\nVue has a feature called slots, which includes three types: default slot, named slot, and scoped slot.\nhttps://vuejs.org/guide/components/slots.html#slots\n\nThis time, we will implement the default slot among them.\nThe desired developer interface is as follows:\n\nhttps://vuejs.org/guide/extras/render-function.html#passing-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () => h('div', {}, [slots.default()])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () => h(MyComponent, {}, () => 'hello')\n  },\n})\n```\n\nThe mechanism is simple. On the slot definition side, we make sure to receive slots as setupContext, and when rendering the component with the h function on the usage side, we simply pass the render function as children.\nPerhaps the most familiar usage for everyone is to place a slot element in the template of SFC, but that requires implementing a separate template compiler, so we will omit it this time. (We will cover it in the Basic Template Compiler section.)\n\nAs usual, add a property that can hold slots to the instance and mix it as SetupContext with createSetupContext.\nModify the h function so that it can receive a render function as the third argument, not just an array, and if a render function is passed, set it as the default slot of the component instance when generating the instance.\nLet's implement it up to this point for now!\n\n(ShapeFlags has been slightly changed due to the implementation of normalize in children.)\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/050_component_slot)\n\n## Implementation of Named Slots/Scoped Slots\n\nThis is an extension of the default slot.\nThis time, let's try passing an object instead of a function.\n\nFor scoped slots, you just need to define the arguments of the render function.\nAs you can see, when using a render function, it doesn't seem necessary to distinguish scoped slots.\nThat's right, the essence of slots is just a callback function, and the API is provided as scoped slots to allow passing arguments to it.\nOf course, we will implement a compiler in the Basic Template Compiler section that can handle scoped slots, but they will be converted to these forms.\n\nhttps://vuejs.org/guide/components/slots.html#scoped-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () =>\n      h('div', {}, [\n        slots.default?.(),\n        h('br', {}, []),\n        slots.myNamedSlot?.(),\n        h('br', {}, []),\n        slots.myScopedSlot2?.({ message: 'hello!' }),\n      ])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h(\n        MyComponent,\n        {},\n        {\n          default: () => 'hello',\n          myNamedSlot: () => 'hello2',\n          myScopedSlot2: (scope: { message: string }) =>\n            `message: ${scope.message}`,\n        },\n      )\n  },\n})\n```\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/060_slot_extend)\n"
  },
  {
    "path": "book/online-book/src/40-basic-component-system/050-options-api.md",
    "content": "# Supporting the Options API\n\n## Options API\n\nSo far, we have been able to implement a lot using the Composition API, but let's also support the Options API.\n\nIn this book, we support the following in the Options API:\n\n- props\n- data\n- computed\n- method\n- watch\n- slot\n- lifecycle\n  - onMounted\n  - onUpdated\n  - onUnmounted\n  - onBeforeMount\n  - onBeforeUpdate\n  - onBeforeUnmount\n- provide/inject\n- $el\n- $data\n- $props\n- $slots\n- $parent\n- $emit\n- $forceUpdate\n- $nextTick\n\nAs an implementation approach, let's prepare a function called \"applyOptions\" in \"componentOptions.ts\" and execute it towards the end of \"setupComponent\".\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n  // ↑ This is the existing implementation\n\n  setCurrentInstance(instance)\n  applyOptions(instance)\n  unsetCurrentInstance()\n}\n```\n\nIn the Options API, the developer interface frequently deals with \"this\".\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { message: 'hello' }\n  },\n\n  methods: {\n    greet() {\n      console.log(this.message) // Like this\n    },\n  },\n})\n```\n\nInternally, \"this\" refers to the component's proxy in the Options API, and when applying options, this proxy is bound.\n\nImplementation example ↓\n\n```ts\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance\n  const publicThis = instance.proxy! as any\n  const ctx = instance.ctx\n\n  const { methods } = options\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key]\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis)\n      }\n    }\n  }\n}\n```\n\nBasically, if you implement them one by one using this principle, it shouldn't be difficult.\n\nIf you want to make \"data\" reactive, call the \"reactive\" function here, and if you want to compute, call the \"computed\" function here. (The same goes for \"provide/inject\")\n\nSince the instance is set by \"setCurrentInstance\" before \"applyOptions\" is executed, you can call the APIs (Composition API) you have been using so far in the same way.\n\nRegarding properties starting with \"$\", they are controlled by the implementation of \"componentPublicInstance\". The getter in \"PublicInstanceProxyHandlers\" controls them.\n\n## Typing Options API\n\nFunctionally, it is fine to implement it as described above, but typing the Options API is a bit complex.\n\nIn this book, we support basic typing for the Options API.\n\nThe difficult point is that the type of \"this\" changes depending on the user's definition of each option. For example, if you define a property called \"count\" of type \"number\" in the \"data\" option, you would want \"this\" in \"computed\" or \"method\" to infer \"count: number\".\n\nOf course, this applies not only to \"data\" but also to those defined in \"computed\" or \"methods\".\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { count: 0 }\n  },\n\n  methods: {\n    myMethod() {\n      this.count // number\n      this.myComputed // number\n    },\n  },\n\n  computed: {\n    myComputed() {\n      return this.count // number\n    },\n  },\n})\n```\n\nTo achieve this, you need to implement a somewhat complex type puzzle (relay with many generics).\n\nStarting with typing for \"defineComponent\", we implement several types to relay to \"ComponentOptions\" and \"ComponentPublicInstance\".\n\nHere, let's focus on \"data\" and \"methods\" for explanation.\n\nFirst, the usual \"ComponentOptions\" type. We extend it with generics to accept the types of \"data\" and \"methods\" as parameters, \"D\" and \"M\".\n\n```ts\nexport type ComponentOptions<\n  D = {},\n  M extends MethodOptions = MethodOptions\n> = {\n  data?: () => D;,\n  methods?: M;\n};\n\ninterface MethodOptions {\n  [key: string]: Function;\n}\n```\n\nSo far, it shouldn't be too difficult. This is the type that can be applied to the arguments of \"defineComponent\".  \nOf course, in \"defineComponent\" as well, we accept \"D\" and \"M\" to relay the user-defined types. This allows us to relay the user-defined types.\n\n```ts\nexport function defineComponent<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n>(options: ComponentOptions<D, M>) {}\n```\n\nThe problem is how to mix \"D\" with \"this\" when dealing with \"this\" in \"methods\" (how to make inference like \"this.count\" possible).\n\nFirst, \"D\" and \"M\" are merged into \"ComponentPublicInstance\" (merged into the proxy). This can be understood as follows (extend with generics).\n\n```ts\ntype ComponentPublicInstance<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n> = {\n  /** Various types that public instance has */\n} & D &\n  M\n```\n\nOnce we have this, we mix the instance type into \"this\" in \"ComponentOptions\".\n\n```ts\ntype ComponentOptions<D = {}, M extends MethodOptions = MethodOptions> = {\n  data?: () => D\n  methods?: M\n} & ThisType<ComponentPublicInstance<D, M>>\n```\n\nBy doing this, we can infer the properties defined in \"data\" and \"method\" from \"this\" in the options.\n\nIn practice, we need to infer various types such as \"props\", \"computed\", and \"inject\", but the basic principle is the same.  \nAt first glance, you may be overwhelmed by the many generics and type conversions (such as extracting only the \"key\" from \"inject\"), but if you calm down and return to the principle, you should be fine.  \nIn the code of this book, inspired by the original Vue, we have abstracted it one step further with \"CreateComponentPublicInstance\" and implemented a type called \"ComponentPublicInstanceConstructor\", but don't worry too much about it. (If you're interested, you can read it too!)\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/070_options_api)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/010-transform.md",
    "content": "# Implementation of Transformer and Codegen Refactoring (Basic Template Compiler Department Start)\n\n## Review of Existing Implementation\n\nNow, let's implement the template compiler more seriously from where we left off in the Minimum Example department. It has been a while since we worked on it, so let's review the current implementation. The main keywords are Parse, AST, and Codegen.\n\n![Minimum compiler pipeline](/figures/50-basic-template-compiler/transform/basic-compiler-pipeline.svg)\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  const code = generate(ast, option)\n  return code\n}\n```\n\nActually, this configuration is slightly different from the original one. Let's take a look at the original code.\n\nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/compile.ts#L61\n\nCan you understand it...?\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  transform(ast)\n  const code = generate(ast, option)\n  return code\n}\n```\n\nIt is like this.\n\nThis time, we will implement the `transform` function.\n\n![Compiler pipeline with transformer](/figures/50-basic-template-compiler/transform/compiler-pipeline-with-transformer.svg)\n\n## What is Transform?\n\nAs you can imagine from the code above, the AST obtained by parsing is transformed in some way by the `transform` function.\n\nYou may get an idea by reading this.  \nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/ast.ts#L43C1-L51C23\n\nThis VNODE_CALL and the AST code with names starting with JS are what we will handle this time.\nVue.js's template compiler is divided into two parts: the AST that represents the result of parsing the template and the AST that represents the generated code.\nOur current implementation only handles the former AST.\n\nLet's consider the case where the template `<p>hello</p>` is given as input.\n\nFirst, the following AST is generated by parsing. This is the same as the existing implementation.\n\n```ts\ninterface ElementNode {\n  tag: string\n  props: object /** omitted */\n  children: (ElementNode | TextNode | InterpolationNode)[]\n}\n\ninterface TextNode {\n  content: string\n}\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {},\n  \"children\": [{ \"content\": \"hello\" }]\n}\n```\n\nAs for the \"AST that represents the generated code,\" let's think about what kind of code should be generated.\nI think it would be something like this:\n\n```ts\nh('p', {}, ['hello'])\n```\n\nThis is the AST that represents the generated JavaScript code.\nIn other words, it is an object that represents the AST for generating the code that should be generated.\n\n```ts\ninterface VNodeCall {\n  tag: string\n  props: PropsExpression\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode // single text child\n    | undefined\n}\n\ntype PropsExpression = ObjectExpression | CallExpression | ExpressionNode\ntype TemplateChildNode = ElementNode | InterpolationNode | TextNode\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {\n    \"type\": \"ObjectExpression\",\n    \"properties\": []\n  },\n  \"children\": { \"content\": \"hello\" }\n}\n```\n\nIn this way, the AST that represents the code generated by Codegen is expressed.\nYou may not feel the need to separate them at this point, but it will be useful when implementing directives in the future.\nBy separating the AST focused on the input and the AST focused on the output, we can perform the transformation from `input AST -> output AST` using the function called `transform`.\n\n## Codegen Node\n\nNow that we have grasped the flow, let's confirm what kind of Node we will handle (what kind of Node we want to convert). I will explain while enumerating them and providing comments. Please refer to the source code for accurate information as some parts are omitted.\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[]\n}\n\n// This represents an expression that calls the h function.\n// It assumes something like `h(\"p\", { class: 'message'}, [\"hello\"])`.\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: ObjectExpression | undefined // NOTE: It is implemented as PropsExpression in the source code (for future extensions)\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode\n\n// This represents a JavaScript Object. It is used for the props of VNodeCall, etc.\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION\n  properties: Array<Property>\n}\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY\n  key: ExpressionNode\n  value: JSChildNode\n}\n\n// This represents a JavaScript Array. It is used for the children of VNodeCall, etc.\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION\n  elements: Array<string | Node>\n}\n```\n\n## Transformer Design\n\nBefore implementing the transformer, let's talk about the design. First, it is important to note that there are two types of transformers: NodeTransform and DirectiveTransform. These are used for transforming nodes and directives, respectively, and take the following interfaces.\n\n```ts\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[]\n\n// TODO:\n// export type DirectiveTransform = (\n//   dir: DirectiveNode,\n//   node: ElementNode,\n//   context: TransformContext,\n// ) => DirectiveTransformResult;\nexport type DirectiveTransform = Function\n```\n\nThe DirectiveTransform will be covered later in the chapter when implementing directives, so for now let's call it Function.\nBoth NodeTransform and DirectiveTransform are actually functions. You can think of them as functions for transforming AST.\nPlease note that the result of NodeTransform is a function. When implementing transform, if you implement it to return a function, that function will be executed after the transformation of that node (it is called the onExit process).\nAny processing you want to perform after the transform of the Node should be described here. I will explain this along with the description of the function called traverseNode later.\nThe explanation of the interface is mainly as described above.\n\nAnd as a more specific implementation, there are transformElement for transforming Element and transformExpression for transforming expressions, etc.\nAs for the implementation of DirectiveTransform, there are implementations for each directive.\nThese implementations are implemented in compiler-core/src/transforms. The specific transformation processes are implemented here.\n\nhttps://github.com/vuejs/core/tree/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/transforms\n\nimage ↓\n\n![Transform type relationships](/figures/50-basic-template-compiler/transform/transform-type-relationships.svg)\n\nNext, about the context, TransformContext holds the information and functions used during these transforms.\nMore will be added in the future, but for now, this is enough.\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n}\n```\n\n## Implementation of Transformer\n\nNow let's take a look at the transform function in practice. First, let's start with a general explanation of the framework that is independent of the content of each transformation process.\n\nThe structure is very simple, just generate the context and traverse the node using the traverseNode function.\nThis traverseNode function is the main implementation of the transformation.\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n}\n```\n\nIn traverseNode, basically, it just applies the nodeTransforms (a collection of functions to transform Node) saved in the context to the node.\nFor those with child nodes, the child nodes are also passed through traverseNode.\nThe implementation of onExit, which was mentioned during the explanation of the interface, is also here.\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  context.currentNode = node\n\n  const { nodeTransforms } = context\n  const exitFns = [] // Operations to be performed after transformation\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context)\n\n    // Register operations to be performed after transformation\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit)\n      } else {\n        exitFns.push(onExit)\n      }\n    }\n    if (!context.currentNode) {\n      return\n    } else {\n      node = context.currentNode\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n\n  context.currentNode = node\n\n  // Execute operations to be performed after transformation\n  let i = exitFns.length\n  while (i--) {\n    exitFns[i]() // Operations that can be executed assuming that the transformation has finished\n  }\n}\n\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    traverseNode(child, context)\n  }\n}\n```\n\nNext, let's talk about the specific transformation process. As an example, let's implement transformElement.\n\nIn transformElement, we mainly convert the node of type NodeTypes.ELEMENT to VNodeCall.\n\n```ts\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n  isSelfClosing: boolean\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\n// ↓↓↓↓↓↓ Transformation ↓↓↓↓↓↓ //\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n```\n\nIt is a simple object to object conversion, so I don't think it is very difficult. Let's try implementing it by reading the source code.  \nI will paste the code that I assume for this time just in case. (The directive support will be done in another chapter.)\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    const { tag, props } = node\n\n    const vnodeTag = `\"${tag}\"`\n    let vnodeProps: VNodeCall['props']\n    let vnodeChildren: VNodeCall['children']\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node)\n      vnodeProps = propsBuildResult.props\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0]\n        const type = child.type\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode\n        } else {\n          vnodeChildren = node.children\n        }\n      } else {\n        vnodeChildren = node.children\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren)\n  }\n}\n\nexport function buildProps(node: ElementNode): {\n  props: PropsExpression | undefined\n  directives: DirectiveNode[]\n} {\n  const { props } = node\n  let properties: ObjectExpression['properties'] = []\n  const runtimeDirectives: DirectiveNode[] = []\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i]\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop\n\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : '', true),\n        ),\n      )\n    } else {\n      // directives\n      // TODO:\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined\n  if (properties.length) {\n    propsExpression = createObjectExpression(properties)\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  }\n}\n```\n\n## Codegen based on the transformed AST\n\nSince we transformed the AST for Codegen, we also need to support Codegen.\nFor the AST that comes into Codegen, it is sufficient to write code assuming VNodeClass (and the nodes they have).\nThe desired final string representation is the same as before.\n\nThe existing Codegen implementation is very simple, so let's make it a little more formal here (because it is quite hard-coded).\nLet's also create a Codegen-specific context and push the generated code into it.\nIn addition, let's implement some helper functions in the context (such as indentation).\n\n```ts\nexport interface CodegenContext {\n  source: string\n  code: string\n  indentLevel: number\n  line: 1\n  column: 1\n  offset: 0\n  push(code: string, node?: CodegenNode): void\n  indent(): void\n  deindent(withoutNewLine?: boolean): void\n  newline(): void\n}\n```\n\nI will omit the implementation details here, but I just separated the functions for each role, and there are no major changes in the implementation approach.\nSince I haven't been able to support directives yet, there are some parts that are not working due to the removal of the temporary implementation in that area, but\nif the code is working roughly as follows, it's OK!\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> Hello World! </p>\n      <p> Count: {{ count }} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nSource code up to this point:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/010_transformer)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/020-v-bind.md",
    "content": "# Let's implement the directive (v-bind).\n\n## Approach\n\nNow let's implement the directive, which is the essence of Vue.js.  \nAs usual, we will apply the directive to the transformer, and the interface that appears there is called DirectiveTransform.  \nDirectiveTransform takes DirectiveNode and ElementNode as arguments and returns the transformed Property.\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult\n\nexport interface DirectiveTransformResult {\n  props: Property[]\n}\n```\n\nFirst, let's check the developer interface we are aiming for this time.\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const bind = { id: 'some-id', class: 'some-class', style: 'color: red' }\n    return { count: 1, bind }\n  },\n\n  template: `<div>\n  <p v-bind:id=\"count\"> v-bind:id=\"count\" </p>\n  <p :id=\"count * 2\"> :id=\"count * 2\" </p>\n\n  <p v-bind:[\"style\"]=\"bind.style\"> v-bind:[\"style\"]=\"bind.style\" </p>\n  <p :[\"style\"]=\"bind.style\"> :[\"style\"]=\"bind.style\" </p>\n\n  <p v-bind=\"bind\"> v-bind=\"bind\" </p>\n\n  <p :style=\"{ 'font-weight': 'bold' }\"> :style=\"{ font-weight: 'bold' }\" </p>\n  <p :style=\"'font-weight: bold;'\"> :style=\"'font-weight: bold;'\" </p>\n\n  <p :class=\"'my-class my-class2'\"> :class=\"'my-class my-class2'\" </p>\n  <p :class=\"['my-class']\"> :class=\"['my-class']\" </p>\n  <p :class=\"{ 'my-class': true }\"> :class=\"{ 'my-class': true }\" </p>\n  <p :class=\"{ 'my-class': false }\"> :class=\"{ 'my-class': false }\" </p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nThere are various notations for v-bind. Please refer to the official documentation for details.  \nWe will also handle class and style.\n\nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\n## AST Modification\n\nFirst, let's modify the AST. Currently, both exp and arg are simple strings, so we need to change them to accept ExpressionNode.\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined // here\n  arg: ExpressionNode | undefined // here\n}\n```\n\nLet me explain name, arg, and exp again.  \nname is the directive name such as v-bind or v-on. It can be on or bind.  \nSince we are implementing v-bind this time, it will be bind.\n\narg is the argument specified by :. For v-bind, it includes id and style.  \n(In the case of v-on, it includes click and input.)\n\nexp is the right side. In the case of v-bind:id=\"count\", count is included.  \nBoth exp and arg can embed variables dynamically, so their types are ExpressionNode.  \n(Since arg can also be dynamic like v-bind:[key]=\"count\")\n\n![DirectiveNode shape for v-bind](/figures/50-basic-template-compiler/v-bind/directive-node-shape.svg)\n\n## Parser Modification\n\nWe will update the parser implementation to follow this AST modification. We will parse exp and arg as SimpleExpressionNode.\n\nWe will also parse @ used in v-on and # used in slots.  \n(Since it's troublesome to consider regular expressions (and it's troublesome to add them gradually while explaining), we will borrow the original code for now.)  \nReference: https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/parse.ts#L802\n\nSince the code is a bit long, I will explain it while writing comments in the code.\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  // .\n  // directive\n  const loc = getSelection(context, start)\n  // The regular expression here is borrowed from the original source\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match =\n      // The regular expression here is borrowed from the original source\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name,\n      )!\n\n    // Check the match of the name part and treat it as \"bind\" if it starts with \":\"\n    let dirName =\n      match[1] ||\n      (startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : '')\n\n    let arg: ExpressionNode | undefined\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2])\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      )\n\n      let content = match[2]\n      let isStatic = true\n\n      // If it is a dynamic argument like \"[arg]\", set isStatic to false and extract the content as the content\n      if (content.startsWith('[')) {\n        isStatic = false\n        if (!content.endsWith(']')) {\n          console.error(`Invalid dynamic argument expression: ${content}`)\n          content = content.slice(1)\n        } else {\n          content = content.slice(1, content.length - 1)\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      }\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    }\n  }\n}\n```\n\nWith this, we were able to parse the AST Node we wanted to handle this time.\n\n## Implementation of Transformer\n\nNext, let's write the implementation to transform this AST into a Codegen AST.  \nSince it is a bit complicated, I summarized the flow in the following diagram. Please take a look at it first.  \nIn general, the necessary items are whether there are arguments for v-bind, whether it is class or style.  \n※ Parts other than the processing involved this time are omitted. (Please note that this diagram is not very strict.)\n\n![v-bind transform flow](/figures/50-basic-template-compiler/v-bind/transform-vbind-flow.svg)\n\nFirst of all, as a premise, since a directive is basically declared for an element,  \nthe transformer related to the directive is called from transformElement.\n\nSince we want to implement v-bind this time, we will implement a function called transformVBind,  \nbut one point to note is that this function only converts declarations that have args.\n\ntransformVBind has the role of converting\n\n```\nv-bind:id=\"count\"\n```\n\ninto an object (actually a Codegen Node representing this object) like\n\n```ts\n{\n  id: count\n}\n```\n\nIn the original implementation as well, the following explanation is given.\n\n> codegen for the entire props object. This transform here is only for v-bind _with_ args.\n\nQuoted from: https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/transforms/vBind.ts#L13C1-L14C16\n\nAs you can see from the flow, transformElement checks the arg of the directive and if it does not exist, it does not execute transformVBind but converts it to a function call to mergeProps.\n\n```vue\n<p v-bind=\"bindingObject\" class=\"my-class\">hello</p>\n```\n\n↓\n\n```ts\nh('p', mergeProps(bindingObject, { class: 'my-class' }), 'hello')\n```\n\nAlso, for class and style, they have various developer interfaces, so they need to be normalized.  \nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\nImplement functions called normalizeClass and normalizeStyle, and apply them respectively.\n\nIf the arg is dynamic, it is impossible to determine the specific one, so implement a function called normalizeProps and call it. (It calls normalizeClass and normalizeStyle internally)\n\nNow that we have implemented this far, let's see how it works!\n\n![v-bind test result in the browser](/figures/50-basic-template-compiler/v-bind/vbind-test-result.png)\n\nLooks great!\n\nNext time, we will implement v-on.\n\nSource code up to this point:  \n[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/020_v_bind)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/022-transform-expression.md",
    "content": "# transformExpression\n\n## Developer Interface to Aim for and Current Challenges\n\nFirst, take a look at this component.\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button :onClick=\"increment\">count + count is: {{ count + count }}</button>\n  </div>\n</template>\n```\n\nThere are several issues with this component.  \nSince this component is written in SFC, the `with` statement is not used.  \nIn other words, the bindings are not working properly.\n\nLet's take a look at the compiled code.\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: increment }), [\n      'count + count is: ',\n      _ctx.count + count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n- Issue 1: The `increment` registered as the event handler cannot access `_ctx`.  \n  This is because the prefix was not added in the previous implementation of `v-bind`.\n- Issue 2: The expression `count + count` cannot access `_ctx`.  \n  Regarding the mustache syntax, it only adds `_ctx.` to the beginning, and cannot handle other identifiers.  \n  Therefore, all identifiers that appear in the middle of an expression need to be prefixed with `_ctx.`. This applies to all parts, not just mustaches.\n\nIt seems that a process is needed to add `_ctx.` to the identifiers that appear in the expressions.\n\n::: details Desired Compilation Result\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: _ctx.increment }), [\n      'count + count is: ',\n      _ctx.count + _ctx.count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n:::\n\n::: warning\n\nActually, the original implementation takes a slightly different approach.\n\nAs you can see below, in the original implementation, anything bound from the `setup` function is resolved through `$setup`.\n\n![Original resolve bindings output](/figures/50-basic-template-compiler/transform-expression/resolve-bindings-original.png)\n\nHowever, implementing this is a bit difficult, so we will simplify it and implement it by adding `_ctx.`. (All props and setup will be resolved from `_ctx`)\n\n:::\n\n## Implementation Approach\n\nTo put it simply, what we want to do is \"add `_ctx.` to the beginning of every Identifier (name) on the ExpressionNode\".\n\nLet me explain it in a bit more detail.  \nAs a review, a program is represented as an AST by being parsed.  \nAnd the AST representing the program has two main types of nodes: Expression and Statement.  \nThese are commonly known as expressions and statements.\n\n```ts\n1 // This is an Expression\nident // This is an Expression\nfunc() // This is an Expression\nident + func() // This is an Expression\n\nlet a // This is a Statement\nif (!a) a = 1 // This is a Statement\nfor (let i = 0; i < 10; i++) a++ // This is a Statement\n```\n\nWhat we want to consider here is Expression.  \nThere are various types of expressions. Identifier is one of them, which is an expression represented by an identifier.  \n(You can think of it as a variable name in general)\n\nIdentifier appears in various places in an expression.\n\n```ts\n1 // None\nident // ident --- (1)\nfunc() // func --- (2)\nident + func() // ident, func --- (3)\n```\n\nIn this way, Identifier appears in various places in an expression.\n\nYou can observe various Identifiers on the ExpressionNode by entering the program on the following site, which allows you to observe the AST.  \nhttps://astexplorer.net/#/gist/670a1bee71dbd50bec4e6cc176614ef8/9a9ff250b18ccd9000ed253b0b6970696607b774\n\n## Searching for Identifiers\n\nNow that we know what we want to do, how do we implement it?\n\nIt seems very difficult, but it is actually simple. We will use a library called estree-walker.  \nhttps://github.com/Rich-Harris/estree-walker\n\nWe will use this library to walk through the AST obtained by parsing with babel.  \nThe usage is very simple. Just pass the AST to the `walk` function and describe the processing for each Node as the second argument.  \nThis `walk` function walks through the AST node by node, and the processing at the point when it reaches that Node is done with the `enter` option.  \nIn addition to `enter`, there are also options such as `leave` to process at the end of that Node. We will only use `enter` this time.\n\nCreate a new file called `compiler-core/babelUtils.ts` and implement utility functions that can perform operations on Identifiers.\n\nFirst, install estree-walker.\n\n```sh\nnpm install estree-walker\n\nnpm install -D @babel/types # Also install this\n```\n\n```ts\nimport { Identifier, Node } from '@babel/types'\n\nimport { walk } from 'estree-walker'\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        onIdentifier(node)\n      }\n    },\n  })\n}\n```\n\nThen, generate the AST for the expression and pass it to this function to perform the transformation while rewriting the nodes.\n\n## Implementation of transformExpression\n\n### Changes to AST and Parser for InterpolationNode\n\nWe will implement the main body of the transformation process, transformExpression.\n\nFirst, we will modify InterpolationNode so that it has a SimpleExpressionNode instead of a string as its content.\n\n```ts\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // [!code --]\n  content: ExpressionNode // [!code ++]\n}\n```\n\nWith this change, we also need to modify parseInterpolation.\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  // .\n  // .\n  // .\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  }\n}\n```\n\n### Implementation of the transformer (main body)\n\nTo make the expression transformation usable in other transformers, we will extract it as a function called `processExpression`.\nIn transformExpression, we will process the ExpressionNode of INTERPOLATION and DIRECTIVE.\n\n```ts\nexport const transformExpression: NodeTransform = node => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp\n        const arg = dir.arg\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          dir.exp = processExpression(exp)\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg)\n        }\n      }\n    }\n  }\n}\n\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // TODO:\n}\n```\n\nNext, let's explain the implementation of processExpression.\nFirst, we will implement a function called rewriteIdentifier to rewrite Identifier within node.\nIf node is a single Identifier, we simply apply this function and return it.\n\nOne thing to note is that this processExpression is specific to SFC (Single File Component) cases (cases without using the with statement).\nIn other words, if the isBrowser flag is set, we implement it to simply return the node.\nWe modify the implementation to receive the flag via ctx.\n\nAlso, I want to leave literals like true and false as they are, so I'll create a whitelist for literals.\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    // Do nothing for the browser\n    return node\n  }\n\n  const rawExp = node.content\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`\n  }\n\n  if (isSimpleIdentifier(rawExp)) {\n    node.content = rewriteIdentifier(rawExp)\n    return node\n  }\n\n  // TODO:\n}\n```\n\n`makeMap` is a helper function for existence checking implemented in vuejs/core, which returns a boolean indicating whether it matches the string defined with comma separation.\n\n```ts\nexport function makeMap(\n  str: string,\n  expectsLowerCase?: boolean,\n): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null)\n  const list: Array<string> = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]\n}\n```\n\nThe problem lies in the next step, which is how to transform the SimpleExpressionNode (not a simple Identifier) and transform the node.\nIn the following discussion, please pay attention to the fact that we will be dealing with two different ASTs: the JavaScript AST generated by Babel and the AST defined by chibivue.\nTo avoid confusion, we will refer to the former as estree and the latter as AST in this chapter.\n\nThe strategy is divided into two stages.\n\n1. Replace the estree node while collecting the node\n2. Build the AST based on the collected node\n\nFirst, let's start with stage 1.\nThis is relatively simple. If we can parse the original SimpleExpressionNode content (string) with Babel and obtain the estree, we can pass it through the utility function we created earlier and apply rewriteIdentifier.\nAt this point, we collect the estree node.\n\n```ts\nimport { parse } from '@babel/parser'\nimport { Identifier } from '@babel/types'\nimport { walkIdentifiers } from '../babelUtils'\n\ninterface PrefixMeta {\n  start: number\n  end: number\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  // .\n  // .\n  // .\n  const ast = parse(`(${rawExp})`).program // ※ This ast refers to estree.\n  type QualifiedId = Identifier & PrefixMeta\n  const ids: QualifiedId[] = []\n\n  walkIdentifiers(ast, node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  })\n\n  // TODO:\n}\n```\n\nOne thing to note is that up to this point, we have only manipulated the estree and have not manipulated the ast node.\n\n### CompoundExpression\n\nNext, let's move on to stage 2. Here, we will define a new AST Node called `CompoundExpressionNode`.\nCompound implies \"combination\" or \"complexity\". This Node has children, which take slightly special values.\nFirst, let's take a look at the definition of AST.\n\n```ts\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n}\n```\n\nChildren takes an array like the one shown above.\nTo understand what children in this Node represent, it would be easier to see specific examples, so let's give some examples.\n\nThe following expression will be parsed into the following CompoundExpressionNode:\n\n```ts\ncount * 2\n```\n\n```json\n{\n  \"type\": 7,\n  \"children\": [\n    {\n      \"type\": 4,\n      \"isStatic\": false,\n      \"content\": \"_ctx.count\"\n    },\n    \" * 2\"\n  ]\n}\n```\n\nIt's quite a strange feeling. The reason why \"children\" takes the type of string is because it takes this form.  \nIn CompoundExpression, the Vue compiler divides it into the necessary granularity and expresses it partially as a string or partially as a Node.  \nSpecifically, in cases like this where an Identifier existing in Expression is rewritten, only the Identifier part is divided into another SimpleExpressionNode.\n\nIn other words, what we are going to do is to generate this CompoundExpression based on the collected estree's Identifier Node and source.  \nThe following code is the implementation for that.\n\n```ts\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // .\n  // .\n  // .\n  const children: CompoundExpressionNode['children'] = []\n  ids.sort((a, b) => a.start - b.start)\n  ids.forEach((id, i) => {\n    const start = id.start - 1\n    const end = id.end - 1\n    const last = ids[i - 1]\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start)\n    if (leadingText.length) {\n      children.push(leadingText)\n    }\n\n    const source = rawExp.slice(start, end)\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    )\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end))\n    }\n  })\n\n  let ret\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc)\n  } else {\n    ret = node\n  }\n\n  return ret\n}\n```\n\nThe Node parsed by Babel has start and end (location information of where it corresponds to the original string), so we extract the corresponding part from rawExp based on that and divide it diligently.  \nPlease take a close look at the source code for more details. If you understand the policy so far, you should be able to read it. (Also, please take a look at the implementation of advancePositionWithClone, etc., as they are newly implemented.)\n\nNow that we can generate CompoundExpressionNode, let's also support it in Codegen.\n\n```ts\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNode(node.content, context, option)\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i]\n    if (isString(child)) {\n      // If it is a string, push it as it is\n      context.push(child)\n    } else {\n      // For anything else, generate codegen for the Node\n      genNode(child, context, option)\n    }\n  }\n}\n```\n\n(genInterpolation has become just genNode, but I'll leave it for now.)\n\n## Try it out\n\nNow that we have implemented this far, let's complete the compiler and try running it!\n\n```ts\n// Add transformExpression\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformElement], { bind: transformBind }] // [!code --]\n  return [[transformExpression, transformElement], { bind: transformBind }] // [!code ++]\n}\n```\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(3)\n    const getMsg = (count: number) => `Count: ${count}`\n    return { count, getMsg }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> {{ 'Message is \"' + getMsg(count) + '\"'}} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/022_transform_expression)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/025-v-on.md",
    "content": "# Support for v-on\n\n## Refactoring\n\nBefore proceeding with the implementation, let's do some refactoring.  \nCurrently, in the code generated by codegen, we are importing (or destructuring) many helper functions exported from `shared` and `runtime-core`.  \nAnd in the implementation of codegen (and transform), we hardcode the function names. This is not very smart.\n\nThis time, let's refactor them as `runtime-helper` and manage them centrally with symbols, and further, change the implementation to only import what is necessary.\n\nFirst, let's implement symbols representing each helper in `compiler-core/runtimeHelpers.ts`.  \nUntil now, we have been using the `h` function for generating VNodes, but this time, let's change it to use `createVNode` following the original implementation.  \nExport `createVNode` from `runtime-core/vnode`, and in `genVNodeCall`, change the code to call `createVNode` instead of `genVNodeCall`.\n\n```ts\nexport const CREATE_VNODE = Symbol()\nexport const MERGE_PROPS = Symbol()\nexport const NORMALIZE_CLASS = Symbol()\nexport const NORMALIZE_STYLE = Symbol()\nexport const NORMALIZE_PROPS = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: 'createVNode',\n  [MERGE_PROPS]: 'mergeProps',\n  [NORMALIZE_CLASS]: 'normalizeClass',\n  [NORMALIZE_STYLE]: 'normalizeStyle',\n  [NORMALIZE_PROPS]: 'normalizeProps',\n}\n```\n\nMake symbols available as `callee` in `CallExpression`.\n\n```ts\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION\n  callee: string | symbol\n}\n```\n\nImplement an area to register helpers and a function to register them in `TransformContext`.\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n  helpers: Map<symbol, number> // This\n  helper<T extends symbol>(name: T): T // This\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n    helpers: new Map(),\n    helper(name) {\n      const count = context.helpers.get(name) || 0\n      context.helpers.set(name, count + 1)\n      return name\n    },\n  }\n\n  return context\n}\n```\n\nReplace the hardcoded parts with this helper function and modify the Preamble to use the registered helpers.\n\n```ts\n// Example)\npropsExpression = createCallExpression('mergeProps', mergeArgs, elementLoc)\n// ↓\npropsExpression = createCallExpression(\n  context.helper(MERGE_PROPS),\n  mergeArgs,\n  elementLoc,\n)\n```\n\nPass `context` to `createVNodeCall` and register `CREATE_VNODE` inside it.\n\n```ts\nexport function createVNodeCall(\n  context: TransformContext | null, // This\n  tag: VNodeCall['tag'],\n  props?: VNodeCall['props'],\n  children?: VNodeCall['children'],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  // Here ------------------------\n  if (context) {\n    context.helper(CREATE_VNODE)\n  }\n  // ------------------------\n\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  }\n}\n```\n\n```ts\nfunction genVNodeCall(\n  node: VNodeCall,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n  const { tag, props, children } = node\n\n  push(helper(CREATE_VNODE) + `(`, node) // Call createVNode\n  genNodeList(genNullableArgs([tag, props, children]), context, option)\n  push(`)`)\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  root.helpers = new Set([...context.helpers.keys()]) // Add helpers to root\n}\n```\n\n```ts\n// Add `_` as a prefix to alias it according to the original implementation\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context\n\n  // Generate helper declarations based on the helpers registered in ast\n  const helpers = Array.from(ast.helpers)\n  push(\n    `const { ${helpers.map(aliasHelper).join(', ')} } = ${runtimeGlobalName}\\n`,\n  )\n  newline()\n}\n```\n\n```ts\n// Handle symbols in genCallExpression and convert them to helper calls.\n\nexport interface CodegenContext {\n  // .\n  // .\n  // .\n  helper(key: symbol): string\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    // .\n    // .\n    // .\n    helper(key) {\n      return `_${helperNameMap[key]}`\n    },\n  }\n  // .\n  // .\n  // .\n  return context\n}\n\n// .\n// .\n// .\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n\n  // If it is a symbol, get it from the helper\n  const callee = isString(node.callee) ? node.callee : helper(node.callee)\n\n  push(callee + `(`, node)\n  genNodeList(node.arguments, context, option)\n  push(`)`)\n}\n```\n\nWith this, the refactoring we are doing this time is complete. We were able to clean up the hardcoded parts!\n\n::: details Compilation Result\n\n※ Note\n\n- The input is using the one from the previous playground\n- There is actually a `return` before the `function`\n- The generated code is formatted with prettier\n\nWhen you look at it like this, there are too many unnecessary line breaks and spaces...\n\nWell, let's improve this somewhere else.\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      normalizeClass: _normalizeClass,\n    } = ChibiVue\n\n    return _createVNode('div', null, [\n      '\\n  ',\n      _createVNode('p', _normalizeProps({ id: count }), ' v-bind:id=\"count\" '),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ id: count * 2 }),\n        ' :id=\"count * 2\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' v-bind:[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' :[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode('p', _normalizeProps(bind), ' v-bind=\"bind\" '),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: { 'font-weight': 'bold' } }),\n        ' :style=\"{ font-weight: \\'bold\\' }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: 'font-weight: bold;' }),\n        ' :style=\"\\'font-weight: bold;\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass('my-class my-class2'),\n        }),\n        ' :class=\"\\'my-class my-class2\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ class: _normalizeClass(['my-class']) }),\n        ' :class=\"[\\'my-class\\']\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': true }),\n        }),\n        ' :class=\"{ \\'my-class\\': true }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': false }),\n        }),\n        ' :class=\"{ \\'my-class\\': false }\" ',\n      ),\n      '\\n',\n    ])\n  }\n}\n```\n\n:::\n\n## v-on\n\n## Developer Interface to Aim for This Time\n\nNow let's move on to the implementation of v-on.\n\nv-on also has various developer interfaces.\nhttps://vuejs.org/guide/essentials/event-handling.html\n\nThis is what we aim for this time.\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    const increment = (e: Event) => {\n      console.log(e)\n      count.value++\n    }\n    return { count, increment, state: { increment }, eventName: 'click' }\n  },\n\n  template: `<div>\n    <p>count: {{ count }}</p>\n\n    <button v-on:click=\"increment\">v-on:click=\"increment\"</button>\n    <button v-on:[eventName]=\"increment\">v-on:click=\"increment\"</button>\n    <button @click=\"increment\">@click=\"increment\"</button>\n    <button v-on=\"{ click: increment }\">v-on=\"{ click: increment }\"</button>\n\n    <button @click=\"state.increment\">v-on:click=\"increment\"</button>\n    <button @click=\"count++\">@click=\"count++\"</button>\n    <button @click=\"() => count++\">@click=\"() => count++\"</button>\n    <button @click=\"increment($event)\">@click=\"increment($event)\"</button>\n    <button @click=\"e => increment(e)\">@click=\"e => increment(e)\"</button>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n## What I want to do\n\nActually, as for the implementation of the Parser, the one from the previous chapter is sufficient, and the problem lies in the implementation of the Transformer.  \nThe content of the transformation changes mainly depending on the presence or absence of arg and the type of exp.  \nAnd when there is no arg, what needs to be done is almost the same as v-bind.\n\nIn other words, what needs to be considered is the types of exp that can be taken as arg and the transformation of the necessary AST Node for them.\n\n- Task 1  \n  Assign a function.  \n  This is the simplest case.\n\n  ```html\n  <button v-on:click=\"increment\">increment</button>\n  ```\n\n- Task 2  \n  Write a function expression on the spot.  \n  In this case, you can receive the event as the first argument.\n\n  ```html\n  <button v-on:click=\"(e) => increment(e)\">increment</button>\n  ```\n\n- Task 3  \n  Write a statement other than a function.\n\n  ```html\n  <button @click=\"count = 0\">reset</button>\n  ```\n\n  It seems that this expression needs to be converted to the following function.\n\n  ```ts\n  ;() => {\n    count = 0\n  }\n  ```\n\n- Task 4  \n  In cases like Task 3, you can use the identifier `$event`.  \n  This is a case where you handle the event object.\n\n  ```ts\n  const App = defineComponent({\n    setup() {\n      const count = ref(0)\n      const increment = (e: Event) => {\n        console.log(e)\n        count.value++\n      }\n      return { count, increment, object }\n    },\n\n    template: `\n      <div class=\"container\">\n        <button @click=\"increment($event)\">increment($event)</button>\n        <p> {{ count }} </p>\n      </div>\n      `,\n  })\n  // Cannot be used like @click=\"() => increment($event)\".\n  ```\n\n  It seems that it needs to be converted to the following function.\n\n  ```ts\n  $event => {\n    increment($event)\n  }\n  ```\n\n## Implementation\n\n### When there is no arg\n\nFor the time being, let's implement the case where there is no arg, as it is the same as v-bind.  \nThis is the part where I left a TODO comment in the previous chapter. It's around transformElement.\n\n```ts\nconst isVBind = name === 'bind'\nconst isVOn = name === 'on' // --------------- Here\n\n// special case for v-bind and v-on with no argument\nif (!arg && (isVBind || isVOn)) {\n  if (exp) {\n    if (isVBind) {\n      pushMergeArg()\n      mergeArgs.push(exp)\n    } else {\n      // -------------------------------------- Here\n      // v-on=\"obj\" -> toHandlers(obj)\n      pushMergeArg({\n        type: NodeTypes.JS_CALL_EXPRESSION,\n        loc,\n        callee: context.helper(TO_HANDLERS),\n        arguments: [exp],\n      })\n    }\n  }\n  continue\n}\n\nconst directiveTransform = context.directiveTransforms[name]\nif (directiveTransform) {\n  const { props } = directiveTransform(prop, node, context)\n  if (isVOn && arg && !isStaticExp(arg)) {\n    pushMergeArg(createObjectExpression(props, elementLoc))\n  } else {\n    properties.push(...props)\n  }\n} else {\n  // TODO: custom directive.\n}\n```\n\nI will implement the helper function called `TO_HANDLERS` this time.\n\nThis function converts an object passed in the form of `v-on=\"{ click: increment }\"` to the form of `{ onClick: increment }`.  \nThere is nothing particularly difficult about it.\n\n```ts\nimport { toHandlerKey } from '../../shared'\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {}\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key]\n  }\n  return ret\n}\n```\n\nThis completes the implementation when there is no arg.  \nLet's move on to the implementation when there is arg.\n\n### transformVOn\n\nNow, let's move on to the main theme of this time, which is v-on. There are various formats for the exp of v-on.\n\n```ts\nincrement\n\nstate.increment\n\ncount++\n\n;() => count++\n\nincrement($event)\n\ne => increment(e)\n```\n\nFirst, these formats can be broadly classified into two categories: \"function\" and \"statement\". In Vue, if it is a single Identifier, a single MemberExpression, or a function expression, it is treated as a function. Otherwise, it is a statement. In the source code, it seems to be referred to as inlineStatement.\n\n```ts\n// function (※ Please consider these as function expressions for convenience.)\nincrement\nstate.increment\n;() => count++\ne => increment(e)\n\n// inlineStatement\ncount++\nincrement($event)\n```\n\nIn other words, the implementation flow for this time is as follows:\n\n1. First, determine whether it is a function or not (a single Identifier or a single MemberExpression or a function expression).\n\n2-1. If it is a function, generate an ObjectProperty in the form of `eventName: exp` without any transformation.\n\n2-2. If it is not a function (if it is an inlineStatement), convert it to the form of `$event => { ${exp} }` and generate an ObjectProperty.\n\nThat's the basic idea.\n\n#### Determining whether it is a function expression or a statement\n\nLet's start by implementing the determination. Whether it is a function expression or not is done using regular expressions.\n\n```ts\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/\n\nconst isFn = fnExpRE.test(exp.content)\n```\n\nAnd whether it is a single Identifier or a single MemberExpression is implemented with a function called `isMemberExpression`.\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\n```\n\nThis `isMemberExpression` function is quite complicated and has a long implementation. It's a bit long, so I'll omit it here. (Please take a look at the code if you're interested.)\n\nOnce we have determined this far, the condition for it to be an inlineStatement is anything other than these.\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isFnExp = fnExpRE.test(exp.content)\nconst isInlineStatement = !(isMemberExp || isFnExp)\n```\n\nNow that we have determined this, let's implement the transformation process based on this result.\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))\nconst hasMultipleStatements = exp.content.includes(`;`)\n\nif (isInlineStatement) {\n  // wrap inline statement in a function expression\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\n### Issues\n\nActually, there is a slight issue with the above implementation.\n\nThe problem is with `$event` because in `dir.exp`, we need to process the values bound from setup using `processExpression`, but the issue is with `$event`.  \nOn the AST, `$event` is also treated as an Identifier, so if we leave it as it is, it will be prefixed with `_ctx.`.\n\nSo let's make a little improvement. Let's register a local variable in `transformContext`. And in `walkIdentifiers`, we won't execute `onIdentifier` if there is a local variable.\n\n```ts\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  identifiers: Object.create(null),\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      addId(exp)\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      removeId(exp)\n    }\n  },\n}\n\nfunction addId(id: string) {\n  const { identifiers } = context\n  if (identifiers[id] === undefined) {\n    identifiers[id] = 0\n  }\n  identifiers[id]!++\n}\n\nfunction removeId(id: string) {\n  context.identifiers[id]!--\n}\n```\n\n```ts\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null), // [!code ++]\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        const isLocal = !!knownIds[node.name] // [!code ++]\n        // prettier-ignore\n        if (!isLocal) { // [!code ++]\n          onIdentifier(node);\n        } // [!code ++]\n      }\n    },\n  })\n}\n```\n\nThen, when using `walkIdentifiers` in `processExpression`, we will pull `identifiers` from `context`.\n\n```ts\nconst ids: QualifiedId[] = []\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers) // [!code ++]\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // [!code ++]\n)\n```\n\nFinally, when transforming in `transformOn`, let's register `$event`.\n\n```ts\n// prettier-ignore\nif (!context.isBrowser) { // [!code ++]\n  isInlineStatement && context.addIdentifiers(`$event`); // [!code ++]\n  exp = dir.exp = processExpression(exp, context); // [!code ++]\n  isInlineStatement && context.removeIdentifiers(`$event`); // [!code ++]\n} // [!code ++]\n\nif (isInlineStatement) {\n  // wrap inline statement in a function expression\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\nSince v-on requires some special handling, and since it is handled individually in `transformOn`, we will skip it in `transformExpression`.\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  // .\n  // .\n  // .\n  if (\n    exp &&\n    exp.type === NodeTypes.SIMPLE_EXPRESSION &&\n    !(dir.name === 'on' && arg) // [!code ++]\n  ) {\n    dir.exp = processExpression(exp, ctx)\n  }\n}\n```\n\nNow, we have finished the key part of this time. Let's implement the remaining necessary parts and complete v-on!!\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/025_v_on)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/027-event-modifier.md",
    "content": "# Event Modifiers\n\n## What to do this time\n\nSince we implemented the v-on directive last time, let's now implement event modifiers.\n\nVue.js has modifiers that correspond to preventDefault and stopPropagation.\n\nhttps://vuejs.org/guide/essentials/event-handling.html\n\nThis time, let's aim for the following developer interface.\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const inputText = ref('')\n\n    const buffer = ref('')\n    const handleInput = (e: Event) => {\n      const target = e.target as HTMLInputElement\n      buffer.value = target.value\n    }\n    const submit = () => {\n      inputText.value = buffer.value\n      buffer.value = ''\n    }\n\n    return { inputText, buffer, handleInput, submit }\n  },\n\n  template: `<div>\n    <form @submit.prevent=\"submit\">\n      <label>\n        Input Data\n        <input :value=\"buffer\" @input=\"handleInput\" />\n      </label>\n      <button>submit</button>\n    </form>\n    <p>inputText: {{ inputText }}</p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nIn particular, please pay attention to the following part.\n\n```html\n<form @submit.prevent=\"submit\"></form>\n```\n\nThere is a description of `@submit.prevent`. This means that when calling the submit event handler, `preventDefault` is executed.\n\nIf you don't include `.prevent`, the page will be reloaded when submitting.\n\n## Implementation of AST and Parser\n\nSince we are adding a new syntax to the template, changes to the Parser and AST are necessary.\n\nFirst, let's take a look at the AST. It's very simple, just add a property called `modifiers` (an array of strings) to `DirectiveNode`.\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined\n  arg: ExpressionNode | undefined\n  modifiers: string[] // Add this\n}\n```\n\nLet's implement the Parser accordingly.\n\nActually, it's very easy because it's already included in the regular expression borrowed from the original source.\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  const modifiers = match[3] ? match[3].slice(1).split('.') : [] // Extract modifiers from the match result\n  return {\n    type: NodeTypes.DIRECTIVE,\n    name: dirName,\n    exp: value && {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      content: value.content,\n      isStatic: false,\n      loc: value.loc,\n    },\n    loc,\n    arg,\n    modifiers, // Include in the return\n  }\n}\n```\n\nYes. With this, the implementation of AST and Parser is complete.\n\n## compiler-dom/transform\n\nLet's review the current compiler architecture a little.\n\nThe current configuration is as follows.\n\n![Compiler architecture before DOM modifiers](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-core-only.svg)\n\nWhen you understand the roles of compiler-core and compiler-dom again,  \ncompiler-core provides compiler functionality that does not depend on the DOM, such as generating and transforming AST.\n\nSo far, we have implemented v-on directive in compiler-core, but this is just converting the notation `@click=\"handle\"` to an object `{ onClick: handle }`,  \nIt does not perform any processing that depends on the DOM.\n\nNow, let's take a look at what we want to implement this time.  \nThis time, we want to generate code that actually executes `e.preventDefault()` or `e.stopPropagation()`.  \nThese depend heavily on the DOM.\n\nTherefore, we will also implement transformers on the compiler-dom side. We will implement transformers related to the DOM here.\n\nIn compiler-core, we need to consider the interaction between the transform in compiler-core and the transform implemented in compiler-dom.  \nThe interaction is how to implement the transform implemented in compiler-dom while executing the transform in compiler-core.\n\nSo first, let's modify the `DirectiveTransform` interface implemented in compiler-core.\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult, // Added\n) => DirectiveTransformResult\n```\n\nI added `augmentor`.  \nWell, this is just a callback function. By allowing callbacks to be received as part of the `DirectiveTransform` interface, we make the transform function extensible.\n\nIn compiler-dom, we will implement a transformer that wraps the transformers implemented in compiler-core.\n\n```ts\n// Implementation example\n\n// Implementation on the compiler-dom side\n\nimport { transformOn as baseTransformOn } from 'compiler-core'\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransformOn(dir, node, context, () => {\n    /** Implement compiler-dom's own implementation here */\n    return {\n      /** */\n    }\n  })\n}\n```\n\nAnd if you pass this `transformOn` implemented on the compiler-dom side as an option to the compiler, it will be OK.  \nHere is a diagram of the relationship.  \nInstead of passing all transformers from compiler-dom, the default implementation is implemented in compiler-core, and the configuration allows additional transformers to be added.\n\n![Compiler architecture with DOM augmentor](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-dom-augmentor.svg)\n\nWith this, compiler-core can execute transformers without depending on the DOM, and compiler-dom can implement processing that depends on the DOM while executing the transformers in compiler-core.\n\n## Implementation of the transformer\n\nNow, let's implement the transformer on the compiler-dom side.\n\nHow should we transform it? For now, since there are various types of modifiers even if we simply say \"modifier,\" let's classify them so that we can consider future possibilities.\n\nThis time, we will implement the \"event modifier\". Let's start by extracting it as `eventModifiers`.\n\n```ts\nconst isEventModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self`,\n)\n\nconst resolveModifiers = (modifiers: string[]) => {\n  const eventModifiers = []\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i]\n    if (isEventModifier(modifier)) {\n      eventModifiers.push(modifier)\n    }\n  }\n\n  return { eventModifiers }\n}\n```\n\nNow that we have extracted `eventModifiers`, how should we use it? In conclusion, we will implement a helper function called `withModifiers` on the runtime-dom side and transform it into an expression that calls that function.\n\n```ts\n// runtime-dom/runtimeHelpers.ts\n\nexport const V_ON_WITH_MODIFIERS = Symbol()\n```\n\n```ts\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, baseResult => {\n    const { modifiers } = dir\n    if (!modifiers.length) return baseResult\n\n    let { key, value: handlerExp } = baseResult.props[0]\n    const { eventModifiers } = resolveModifiers(modifiers)\n\n    if (eventModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(eventModifiers),\n      ])\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    }\n  })\n}\n```\n\nWith this, the implementation of the transformer is almost complete.\n\nNow let's implement `withModifiers` on the compiler-dom side.\n\n## Implementation of `withModifiers`\n\nLet's proceed with the implementation in runtime-dom/directives/vOn.ts.\n\nThe implementation is very simple.\n\nImplement a guard function for event modifiers and implement it so that it runs as many times as the number of modifiers received in an array.\n\n```ts\nconst modifierGuards: Record<string, (e: Event) => void | boolean> = {\n  stop: e => e.stopPropagation(),\n  prevent: e => e.preventDefault(),\n  self: e => e.target !== e.currentTarget,\n}\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]]\n      if (guard && guard(event)) return\n    }\n    return fn(event, ...args)\n  }\n}\n```\n\nThat's the end of the implementation.\n\nLet's check the operation! If the input content is reflected on the screen without the page being reloaded when the button is pressed, it's OK!\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier)\n\n## Other Modifiers\n\nNow that we've come this far, let's implement other modifiers.\n\nThe basic implementation approach is the same.\n\nLet's classify the modifiers as follows:\n\n```ts\nconst keyModifiers = []\nconst nonKeyModifiers = []\nconst eventOptionModifiers = []\n```\n\nThen, generate the necessary maps and classify them with `resolveModifiers`.\n\nThe two points to be careful about are:\n\n- The difference between the modifier name and the actual DOM API name\n- Implementing a new helper function to execute with specific key events (withKeys)\n\nPlease try implementing while reading the actual code!\nIf you've come this far, you should be able to do it.\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier2)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/030-fragment.md",
    "content": "# Implementing Fragment\n\n## Issues with the current implementation\n\nLet's try running the following code in a playground:\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nYou may encounter an error like this:\n\n![Fragment error result in the browser](/figures/50-basic-template-compiler/fragment/fragment-error-result.png)\n\nLooking at the error message, it seems to be related to the Function constructor.\n\nIn other words, the code generation seems to be successful up to a certain point, so let's see what code is actually generated.\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode } = ChibiVue\n\n    return _createVNode(\"header\", null, \"header\")\"\\n  \"_createVNode(\"main\", null, \"main\")\"\\n  \"_createVNode(\"footer\", null, \"footer\")\n   }\n}\n```\n\nThe code after the `return` statement is incorrect. The current code generation implementation does not handle cases where the root is an array (i.e., not a single node).\n\nWe will fix this issue.\n\n## What code should be generated?\n\nEven though we are making modifications, what kind of code should be generated?\n\nIn conclusion, the code should look like this:\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode, Fragment: _Fragment } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      [\n        _createVNode('header', null, 'header'),\n        '\\n  ',\n        _createVNode('main', null, 'main'),\n        '\\n  ',\n        _createVNode('footer', null, 'footer'),\n      ],\n    ])\n  }\n}\n```\n\nThis `Fragment` is a symbol defined in Vue.\n\nIn other words, Fragment is not represented as an AST like FragmentNode, but simply as a tag of ElementNode.\n\nWe will implement the processing for Fragment in the renderer, similar to Text.\n\n## Implementation\n\nThe Fragment symbol will be implemented in runtime-core/vnode.ts.\n\nLet's add it as a new type in VNodeTypes.\n\n```ts\nexport type VNodeTypes = Component | typeof Text | typeof Fragment | string\n\nexport const Fragment = Symbol()\n```\n\nImplement the renderer.\n\nAdd a branch for fragment in the patch function.\n\n```ts\nif (type === Text) {\n  processText(n1, n2, container, anchor)\n} else if (shapeFlag & ShapeFlags.ELEMENT) {\n  processElement(n1, n2, container, anchor, parentComponent)\n} else if (type === Fragment) {\n  // Here\n  processFragment(n1, n2, container, anchor, parentComponent)\n} else if (shapeFlag & ShapeFlags.COMPONENT) {\n  processComponent(n1, n2, container, anchor, parentComponent)\n} else {\n  // do nothing\n}\n```\n\nNote that inserting or removing elements should generally be implemented with anchor as a marker.\n\nAs the name suggests, an anchor indicates the start and end positions of a fragment.\n\nThe starting element is represented by the existing `el` property in VNode, but currently there is no property to represent the end. Let's add it.\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  anchor: HostNode | null // fragment anchor // Added\n  // .\n  // .\n}\n```\n\nSet the anchor during mount.\n\nPass the fragment's end as the anchor in mount/patch.\n\n```ts\nconst processFragment = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!\n  const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!\n\n  if (n1 == null) {\n    hostInsert(fragmentStartAnchor, container, anchor)\n    hostInsert(fragmentEndAnchor, container, anchor)\n    mountChildren(\n      n2.children as VNode[],\n      container,\n      fragmentEndAnchor,\n      parentComponent,\n    )\n  } else {\n    patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent)\n  }\n}\n```\n\nBe careful when the elements of the fragment change during updates.\n\n```ts\nconst move = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const { type, children, el, shapeFlag } = vnode\n\n  // .\n\n  if (type === Fragment) {\n    hostInsert(el!, container, anchor)\n    for (let i = 0; i < (children as VNode[]).length; i++) {\n      move((children as VNode[])[i], container, anchor)\n    }\n    hostInsert(vnode.anchor!, container, anchor) // Insert the anchor\n    return\n  }\n  // .\n  // .\n  // .\n}\n```\n\nDuring unmount, also rely on the anchor to remove elements.\n\n```ts\nconst remove = (vnode: VNode) => {\n  const { el, type, anchor } = vnode\n  if (type === Fragment) {\n    removeFragment(el!, anchor!)\n  }\n\n  // .\n  // .\n  // .\n}\n\nconst removeFragment = (cur: RendererNode, end: RendererNode) => {\n  let next\n  while (cur !== end) {\n    next = hostNextSibling(cur)! // ※ Add this to nodeOps!\n    hostRemove(cur)\n    cur = next\n  }\n  hostRemove(end)\n}\n```\n\n## Testing\n\nThe code we wrote earlier should work correctly.\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nCurrently, we cannot use directives like v-for, so we cannot write a description that uses a fragment in the template and changes the number of elements.\n\nLet's simulate the behavior by writing the compiled code and see how it works.\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\n// const App = defineComponent({\n//   template: `<header>header</header>\n//   <main>main</main>\n//   <footer>footer</footer>`,\n// });\n\nconst App = defineComponent({\n  setup() {\n    const list = ref([0])\n    const update = () => {\n      list.value = [...list.value, list.value.length]\n    }\n    return () =>\n      h(Fragment, {}, [\n        h('button', { onClick: update }, 'update'),\n        ...list.value.map(i => h('div', {}, i)),\n      ])\n  },\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nIt seems to be working correctly!\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/030_fragment)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/035-comment.md",
    "content": "# Implementing Comment Out\n\n## Target Developer Interface\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `\n  <!-- this is header. -->\n  <header>header</header>\n\n  <!-- \n    this is main.\n    main content is here!\n  -->\n  <main>main</main>\n\n  <!-- this is footer -->\n  <footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nThere is no need for further explanation.\n\n## Implementation of AST and Parser\n\nAs for how to implement comment out, at first glance, it seems that we can simply ignore it when parsing.\n\nHowever, in Vue, comment outs written in the template are output as HTML as they are.\n\nIn other words, comment outs also need to be rendered, so it is necessary to have a representation on the VNode and the compiler also needs to output that code.\nIn addition, an operation to generate a comment node is also necessary.\n\nFirst, let's implement the AST and parser.\n\n### AST\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  COMMENT,\n  // .\n  // .\n  // .\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT\n  content: string\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n```\n\n### Parser\n\nFor now, let's throw an error.\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  // .\n  // .\n  // .\n  if (startsWith(s, '{{')) {\n    node = parseInterpolation(context)\n  } else if (s[0] === '<') {\n    if (s[1] === '!') {\n      // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n      if (startsWith(s, '<!--')) {\n        node = parseComment(context)\n      }\n    } else if (/[a-z]/i.test(s[1])) {\n      node = parseElement(context, ancestors)\n    }\n  }\n  // .\n  // .\n  // .\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context)\n  let content: string\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source)\n  if (!match) {\n    content = context.source.slice(4)\n    advanceBy(context, context.source.length)\n    throw new Error('EOF_IN_COMMENT') // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error('ABRUPT_CLOSING_OF_EMPTY_COMMENT') // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error('INCORRECTLY_CLOSED_COMMENT') // TODO: error handling\n    }\n    content = context.source.slice(4, match.index)\n\n    const s = context.source.slice(0, match.index)\n    let prevIndex = 1,\n      nestedIndex = 0\n    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1)\n      if (nestedIndex + 4 < s.length) {\n        throw new Error('NESTED_COMMENT') // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1)\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n## Generating Code\n\nAdd a VNode that represents Comment to the runtime-core.\n\n```ts\nexport const Comment = Symbol()\nexport type VNodeTypes =\n  | string\n  | Component\n  | typeof Text\n  | typeof Comment\n  | typeof Fragment\n```\n\nImplement a function called createCommentVNode and expose it as a helper.\n\nIn codegen, generate the code that calls createCommentVNode.\n\n```ts\nexport function createCommentVNode(text: string = ''): VNode {\n  return createVNode(Comment, null, text)\n}\n```\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    // .\n    // .\n    // .\n    case NodeTypes.COMMENT:\n      genComment(node, context)\n      break\n    // .\n    // .\n    // .\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)\n}\n```\n\n## Rendering\n\nLet's implement the renderer.\n\nAs usual, branch the case of Comment in patch and generate a comment when mounting.\n\nRegarding the patch, since it's static this time, I won't be doing anything special. (In the code, it's just set to assign as is.)\n\n```ts\nconst patch = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const { type, ref, shapeFlag } = n2\n  if (type === Text) {\n    processText(n1, n2, container, anchor)\n  } else if (type === Comment) {\n    processCommentNode(n1, n2, container, anchor)\n  } //.\n  //.\n  //.\n}\n\nconst processCommentNode = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  if (n1 == null) {\n    hostInsert(\n      (n2.el = hostCreateComment((n2.children as string) || '')), // Implement hostCreateComment on the nodeOps side!\n      container,\n      anchor,\n    )\n  } else {\n    n2.el = n1.el\n  }\n}\n```\n\nWell, you should have implemented comment out by now. Let's check the actual operation!\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/035_comment)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/040-v-if-and-structural-directive.md",
    "content": "# v-if and structural directives\n\nNow let's continue implementing directives!\n\nFinally, we will implement v-if.\n\n## Difference between v-if directive and previous directives\n\nSo far, we have implemented directives such as v-bind and v-on.\n\nNow let's implement v-if, but v-if is slightly different from these directives.\n\nAccording to the excerpt from the official Vue.js documentation on compile-time optimization,\n\n> In this case, the entire template has a single block because it does not contain any structural directives like v-if and v-for.\n\nhttps://vuejs.org/guide/extras/rendering-mechanism.html#tree-flattening\n\nAs you can see, the words \"structural directives\" can be found. (You don't have to worry about what Tree Flattening is, as it will be explained separately.)\n\nAs mentioned, v-if and v-for are called \"structural directives\" and are directives that involve the structure.\n\nIn Angular's documentation, they are explicitly mentioned as well.\n\nhttps://angular.jp/guide/structural-directives\n\nv-if and v-for are directives that not only change the attributes (and behavior for events) of elements, but also change the structure of elements by toggling their existence or generating/removing elements based on the number of items in a list.\n\n## Desired developer interface\n\nLet's think about how to combine v-if / v-else-if / v-else to implement FizzBuzz.\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const n = ref(1)\n    const inc = () => {\n      n.value++\n    }\n\n    return { n, inc }\n  },\n\n  template: `\n    <button @click=\"inc\">inc</button>\n    <p v-if=\"n % 5 === 0 && n % 3 === 0\">FizzBuzz</p>\n    <p v-else-if=\"n % 5 === 0\">Buzz</p>\n    <p v-else-if=\"n % 3 === 0\">Fizz</p>\n    <p v-else>{{ n }}</p>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nFirst, let's think about the code we want to generate.\n\nTo put it simply, v-if and v-else are converted into conditional expressions as follows:\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\nAs you can see, we are adding a structure to the code we have implemented so far.\n\nTo implement a transformer that transforms the AST into such code, we need to make some modifications.\n\n::: warning\n\nThe current implementation does not handle whitespace and other skipping, so there may be unnecessary text nodes in between.\n\nHowever, there is no problem with the implementation of v-if (you will see later), so please ignore it for now.\n\n:::\n\n## Implementation of structural directives\n\n### Implement methods related to structure\n\nBefore implementing v-if, let's do some preparation.\n\nAs mentioned earlier, v-if and v-for are structural directives that modify the structure of AST nodes.\n\nTo achieve this, we need to implement several methods in the base transformer.\n\nSpecifically, we will implement the following three methods in TransformContext:\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  replaceNode(node: TemplateChildNode): void // Added\n  removeNode(node?: TemplateChildNode): void // Added\n  onNodeRemoved(): void // Added\n}\n```\n\nSince you are already implementing traverseChildren, I think you are already keeping track of the current parent and the index of the children. You can use them to implement the above methods.\n\n<!-- NOTE: You may not need to implement this chapter yet. -->\n\n::: details Just in case\n\nThis part:\n\nI think you have already implemented it, but I will explain it just in case because I didn't explain it in detail in the chapter where it was implemented.\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent // This\n    context.childIndex = i // This\n    traverseNode(child, context)\n  }\n}\n```\n\n:::\n\n```ts\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n\n    // Replaces the current node and the corresponding parent's children with the given node\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node\n    },\n\n    // Removes the given node from the current node's parent's children\n    removeNode(node) {\n      const list = context.parent!.children\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null\n        context.onNodeRemoved()\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--\n          context.onNodeRemoved()\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1)\n    },\n\n    // This is registered when using replaceNode, etc.\n    onNodeRemoved: () => {},\n  }\n\n  return context\n}\n```\n\nSome modifications are also needed in the existing implementation. Adjust traverseChildren to handle the case where removeNode is called.\n\nSince the index changes when a node is removed, decrease the index when a node is removed.\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  let i = 0 // This\n  const nodeRemoved = () => {\n    i-- // This\n  }\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    context.onNodeRemoved = nodeRemoved // This\n    traverseNode(child, context)\n  }\n}\n```\n\n### Implementation of createStructuralDirectiveTransform\n\nTo implement directives such as v-if and v-for, we will implement a helper function called createStructuralDirectiveTransform.\n\nThese transformers only act on NodeTypes.ELEMENT and apply the implementation of each transformer to the DirectiveNode that the Node has.\n\nWell, the implementation itself is not big, so I think it would be easier to understand if you actually see it. It looks like this:\n\n```ts\n// Each transformer (v-if/v-for, etc.) is implemented according to this interface.\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void)\n\nexport function createStructuralDirectiveTransform(\n  // The name also supports regular expressions.\n  // For example, in the transformer for v-if, it is assumed to receive something like /^(if|else|else-if)$/.\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name)\n    ? (n: string) => n === name\n    : (n: string) => name.test(n)\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      // Only act on NodeTypes.ELEMENT\n      const { props } = node\n      const exitFns = []\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i]\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          // Execute the transformer for NodeTypes.DIRECTIVE that matches the name\n          props.splice(i, 1)\n          i--\n          const onExit = fn(node, prop, context)\n          if (onExit) exitFns.push(onExit)\n        }\n      }\n      return exitFns\n    }\n  }\n}\n```\n\n## Implementing v-if\n\n### AST Implementation\n\nPreparations are complete up to this point. From here, let's implement v-if.\n\nAs usual, let's start with the definition of the AST and implement the parser.\n\nI would like to say that, but it seems that we don't need a parser this time.\n\nRather, this time we will think about how we want the transformed AST to look like and implement transformers to transform it accordingly.\n\nLet's take a look at the compiled code that was assumed at the beginning.\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\nIt can be seen that it is ultimately converted to a conditional expression (ternary operator).\n\nSince we have never dealt with conditional expressions before, it seems that we need to handle this in the AST side for Codegen.\nBasically, we want to consider three pieces of information (because it's a \"ternary\" operator).\n\n- **Condition**  \n  This is the part that corresponds to A in A ? B : C.  \n  It is represented by the name \"condition\".\n- **Node when the condition matches**  \n  This is the part that corresponds to B in A ? B : C.  \n  It is represented by the name \"consequent\".\n- **Node when the condition does not match**  \n  This is the part that corresponds to C in A ? B : C.  \n  It is represented by the name \"alternate\".\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION\n  test: JSChildNode\n  consequent: JSChildNode\n  alternate: JSChildNode\n  newline: boolean\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n\nexport function createConditionalExpression(\n  test: ConditionalExpression['test'],\n  consequent: ConditionalExpression['consequent'],\n  alternate: ConditionalExpression['alternate'],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  }\n}\n```\n\nWe will implement an AST to represent the VIf node using these.\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  IF,\n  IF_BRANCH,\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF\n  branches: IfBranchNode[]\n  codegenNode?: IfConditionalExpression\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall\n  alternate: VNodeCall | IfConditionalExpression\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH\n  condition: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  userKey?: AttributeNode | DirectiveNode\n}\n\nexport type ParentNode = RootNode | ElementNode | IfBranchNode\n```\n\n### Implementation of the transformer\n\nNow that we have the AST, let's implement the transformer that generates this AST.\n\nThe idea is to generate an `IfNode` based on several `ElementNode`s.\n\nBy \"several\", in this case, it means that if there are multiple `ElementNode`s, we need to generate a single `IfNode` that includes `v-if` to `v-else` statements.\n\nIf the first `v-if` matches, we need to generate the `IfNode` while checking if the subsequent nodes are `v-else-if` or `v-else`.\n\nLet's start by implementing the overall structure, using the `createStructuralDirectiveTransform` we implemented earlier.\n\nSpecifically, since we want to eventually fill the `codegenNode` with the AST we implemented earlier, we will generate the Node in the `onExit` of this transformer.\n\n```ts\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!)\n          parentCondition.alternate = createCodegenNodeForBranch(\n            branch,\n            context,\n          )\n        }\n      }\n    })\n  },\n)\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // TODO:\n}\n```\n\n```ts\n/// Functions used to generate codegenNode\n\n// Generate codegenNode for branch\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      // The alternate is temporarily set to be generated as a comment.\n      // It will be replaced with the target Node when v-else-if or v-else is encountered.\n      // This is the part where `parentCondition.alternate = createCodegenNodeForBranch(branch, context);` is written.\n      // If v-else-if or v-else is not encountered, it will remain as a CREATE_COMMENT Node.\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', 'true']),\n    ) as IfConditionalExpression\n  } else {\n    return createChildrenCodegenNode(branch, context)\n  }\n}\n\nfunction createChildrenCodegenNode(\n  branch: IfBranchNode,\n  context: TransformContext,\n): VNodeCall {\n  // Just extract vnode call from the branch\n  const { children } = branch\n  const firstChild = children[0]\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall\n  return vnodeCall\n}\n\nfunction getParentCondition(\n  node: IfConditionalExpression,\n): IfConditionalExpression {\n  // Get the end Node by tracing from the node\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate\n      } else {\n        return node\n      }\n    }\n  }\n}\n```\n\nIn `processIf`, more specific AST node transformations are performed.\n\nThere are cases for if / else-if / else, but let's first consider the case for `if`.\n\nThis is quite simple. We create an IfNode and execute the codegenNode generation.\nAt this time, we generate the current Node as an IfBranch and assign it to IfNode, then replace it with IfNode.\n\n```\n- parent\n  - currentNode\n\n↓\n\n- parent\n  - IfNode\n    - IfBranch (currentNode)\n```\n\nThis is the image of changing the structure.\n\n```ts\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // We will run processExpression on exp in advance.\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)\n  }\n\n  if (dir.name === 'if') {\n    const branch = createIfBranch(node, dir)\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    }\n    context.replaceNode(ifNode)\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true)\n    }\n  } else {\n    // TODO:\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === 'else' ? undefined : dir.exp,\n    children: [node],\n  }\n}\n```\n\nLet's consider the case other than v-if.\n\nWe will traverse from the parent's children through the context to obtain the siblings.  \nWe will loop through the nodes (starting from the current node itself) and generate IfBranch based on itself, pushing them into branches.  \nDuring this process, comments and empty texts will be removed.\n\n```ts\nif (dir.name === 'if') {\n  /** omitted */\n} else {\n  const siblings = context.parent!.children\n  let i = siblings.indexOf(node)\n  while (i-- >= -1) {\n    const sibling = siblings[i]\n    if (sibling && sibling.type === NodeTypes.COMMENT) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (\n      sibling &&\n      sibling.type === NodeTypes.TEXT &&\n      !sibling.content.trim().length\n    ) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (sibling && sibling.type === NodeTypes.IF) {\n      context.removeNode()\n      const branch = createIfBranch(node, dir)\n      sibling.branches.push(branch)\n      const onExit = processCodegen && processCodegen(sibling, branch, false)\n      traverseNode(branch, context)\n      if (onExit) onExit()\n      context.currentNode = null\n    }\n    break\n  }\n}\n```\n\nAs you can see, actually else-if and else are not distinguished.\n\nEven in the AST, if there is no condition, it is defined as else, so there is nothing special to consider.  \n(Absorbed in the part of `createIfBranch` with `dir.name === \"else\" ? undefined : dir.exp`)\n\nWhat is important is to generate `IfNode` when it is an `if`, and for other cases, just push them into the branches of that Node.\n\nWith this, the implementation of transformIf is complete. We just need to make a few adjustments around it.\n\nIn traverseNode, we will execute traverseNode for the branches that IfNode has.\n\nWe will also include IfBranch as a target for traverseChildren.\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  // .\n  // .\n  // .\n  switch (node.type) {\n    // .\n    // .\n    // Added\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context)\n      }\n      break\n\n    case NodeTypes.IF_BRANCH: // Added\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n}\n```\n\nFinally, we just need to register transformIf as an option in the compiler.\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformElement],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\nWith this, the transformer is implemented!\n\nAll that's left is to implement codegen, and v-if will be complete. We're almost there, let's do our best!\n\n### Implementation of codegen\n\nThe rest is easy. Just generate code based on the Node of ConditionalExpression.\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.IF: // Don't forget to add this!\n      genNode(node.codegenNode!, context, option)\n      break\n    // .\n    // .\n    // .\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option)\n      break\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break\n  }\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node\n  const { push, indent, deindent, newline } = context\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context)\n  } else {\n    push(`(`)\n    genNode(test, context, option)\n    push(`)`)\n  }\n  needNewline && indent()\n  context.indentLevel++\n  needNewline || push(` `)\n  push(`? `)\n  genNode(consequent, context, option)\n  context.indentLevel--\n  needNewline && newline()\n  needNewline || push(` `)\n  push(`: `)\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION\n  if (!isNested) {\n    context.indentLevel++\n  }\n  genNode(alternate, context, option)\n  if (!isNested) {\n    context.indentLevel--\n  }\n  needNewline && deindent(true /* without newline */)\n}\n```\n\nAs usual, we are simply generating the conditional expression based on the AST, so there is nothing particularly difficult.\n\n## Done!!\n\nWell, it's been a while since we had a slightly fat chapter, but with this, the implementation of v-if is complete! (Good job!)\n\nLet's try running it for real!!\n\nIt's working properly!\n\n![v-if FizzBuzz result in the browser](/figures/50-basic-template-compiler/v-if/fizzbuzz-result.png)\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/040_v_if_and_structural_directive)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/050-v-for.md",
    "content": "# Supporting the v-for directive\n\n## Developer Interface to Aim for\n\nNow, let's continue with the directive implementation. This time, let's try to support v-for.\n\nWell, I think it's a familiar directive for those of you who have used Vue.js before.\n\nThere are various syntaxes for v-for.\nThe most basic one is looping through an array, but you can also loop through other things such as strings, object keys, ranges, and so on.\n\nhttps://vuejs.org/v2/guide/list.html\n\nIt's a bit long, but this time, let's aim for the following developer interface:\n\n```vue\n<script>\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst genId = () => Math.random().toString(36).slice(2)\n\nconst FRUITS_FACTORIES = [\n  () => ({ id: genId(), name: 'apple', color: 'red' }),\n  () => ({ id: genId(), name: 'banana', color: 'yellow' }),\n  () => ({ id: genId(), name: 'grape', color: 'purple' }),\n]\n\nexport default {\n  setup() {\n    const fruits = ref([...FRUITS_FACTORIES].map(f => f()))\n    const addFruit = () => {\n      fruits.value.push(\n        FRUITS_FACTORIES[Math.floor(Math.random() * FRUITS_FACTORIES.length)](),\n      )\n    }\n    return { fruits, addFruit }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"addFruit\">add fruits!</button>\n\n  <!-- basic -->\n  <ul>\n    <li v-for=\"fruit in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- indexed -->\n  <ul>\n    <li v-for=\"(fruit, i) in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- destructuring -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">{{ name }}</span>\n    </li>\n  </ul>\n\n  <!-- object -->\n  <ul>\n    <li v-for=\"(value, key, idx) in fruits[0]\" :key=\"key\">\n      [{{ idx }}] {{ key }}: {{ value }}\n    </li>\n  </ul>\n\n  <!-- range -->\n  <ul>\n    <li v-for=\"n in 10\">{{ n }}</li>\n  </ul>\n\n  <!-- string -->\n  <ul>\n    <li v-for=\"c in 'hello'\">{{ c }}</li>\n  </ul>\n\n  <!-- nested -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">\n        <span v-for=\"n in 3\">{{ n }}</span>\n        <span>{{ name }}</span>\n      </span>\n    </li>\n  </ul>\n</template>\n```\n\nYou might think, \"Are we implementing so many things all of a sudden? It's impossible!\" But don't worry, I will explain step by step.\n\n## Implementation Approach\n\nFirst, let's think about how we want to compile it roughly, and consider where the difficult points might be when implementing it.\n\nFirst, let's take a look at the desired compilation result.\n\nThe basic structure is not so difficult. We will implement a helper function called renderList in the runtime-core to render the list, and compile it into an expression.\n\nExample 1:\n\n```html\n<!-- input -->\n<li v-for=\"fruit in fruits\" :key=\"fruit.id\">{{ fruit.name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\nExample 2:\n\n```html\n<!-- input -->\n<li v-for=\"(fruit, idx) in fruits\" :key=\"fruit.id\">\n  {{ idx }}: {{ fruit.name }}\n</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\nExample 3:\n\n```html\n<!-- input -->\n<li v-for=\"{ name, id } in fruits\" :key=\"id\">{{ name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, ({ name, id }) => h('li', { key: id }, name)),\n)\n```\n\nIn the future, the values passed as the first argument to renderList are expected to be not only arrays but also numbers and objects. However, for now, let's assume that only arrays are expected. The implementation of the \\_renderList function itself can be understood as something similar to Array.prototype.map. As for values other than arrays, you just need to normalize them in \\_renderList, so let's forget about them for now (just focus on arrays).\n\nNow, for those of you who have implemented various directives so far, implementing this kind of compiler (transformer) should not be too difficult.\n\n## Key implementation points (difficult points)\n\nThe difficult point is when using it in SFC (Single File Components). Do you remember the difference between the compiler used in SFC and the one used in the browser? Yes, it's resolving expressions using `_ctx`.\n\nIn v-for, user-defined local variables appear in various forms, so you need to collect them properly and skip rewriteIdentifiers.\n\n```ts\n// Bad example\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // It's okay to have a prefix for fruits because it is bound from _ctx\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: _ctx.id }, // It's not okay to have _ctx here\n        _ctx.name, // It's not okay to have _ctx here\n      ),\n  ),\n)\n```\n\n```ts\n// Good example\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // It's okay to have a prefix for fruits because it is bound from _ctx\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: id }, // It's not okay to have _ctx here\n        name, // It's not okay to have _ctx here\n      ),\n  ),\n)\n```\n\nThere are various definitions of local variables, from example 1 to 3.\n\nYou need to analyze each definition and collect the identifiers to be skipped.\n\nNow, let's put aside how to achieve this and start implementing it from the big picture.\n\n## Implementation of AST\n\nFor now, let's define the AST as usual.\n\nAs with v-if, we will consider the transformed AST (no need to implement the parser).\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  FOR, // [!code ++]\n  // .\n  // .\n  JS_FUNCTION_EXPRESSION, // [!code ++]\n}\n\nexport type ParentNode =\n  | RootNode\n  | ElementNode\n  | ForNode // [!code ++]\n  | IfBranchNode\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR\n  source: ExpressionNode\n  valueAlias: ExpressionNode | undefined\n  keyAlias: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  parseResult: ForParseResult // To be explained later\n  codegenNode?: ForCodegenNode\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true\n  tag: typeof FRAGMENT\n  props: undefined\n  children: ForRenderListExpression\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST // To be explained later\n  arguments: [ExpressionNode, ForIteratorExpression]\n}\n\n// Also support function expressions because callback functions are used as the second argument of renderList.\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n}\n\n// In the case of v-for, the return is fixed, so it is represented as an AST for that purpose.\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression // [!code ++]\n```\n\nRegarding `RENDER_LIST`, as usual, add it to `runtimeHelpers`.\n\n```ts\n// runtimeHelpers.ts\n// .\n// .\n// .\nexport const RENDER_LIST = Symbol() // [!code ++]\n\nexport const helperNameMap: Record<symbol, string> = {\n  // .\n  // .\n  [RENDER_LIST]: `renderList`, // [!code ++]\n  // .\n  // .\n}\n```\n\nAs for `ForParseResult`, its definition is in `transform/vFor`.\n\n```ts\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n```\n\nTo explain what each of them refers to,\n\nIn the case of `v-for=\"(fruit, i) in fruits\"`,\n\n- source: `fruits`\n- value: `fruit`\n- key: `i`\n- index: `undefined`\n\n`index` is the third argument when applying an object to `v-for`.\n\nhttps://vuejs.org/v2/guide/list.html#v-for-with-an-object\n\n![ForParseResult shape](/figures/50-basic-template-compiler/v-for/for-parse-result.svg)\n\nRegarding `value`, if you use destructuring assignment like `{ id, name, color, }`, it will have multiple identifiers.\n\nWe collect the identifiers defined by `value`, `key`, and `index`, and skip adding the prefix.\n\n## Implementation of codegen\n\nAlthough the order is a bit out of order, let's implement codegen first because there is not much to talk about.\nThere are only two things to do: handling `NodeTypes.FOR` and codegen for function expressions (which turned out to be the first appearance).\n\n```ts\nswitch (node.type) {\n  case NodeTypes.ELEMENT:\n  case NodeTypes.FOR: // [!code ++]\n  case NodeTypes.IF:\n  // .\n  // .\n  // .\n  case NodeTypes.JS_FUNCTION_EXPRESSION: // [!code ++]\n    genFunctionExpression(node, context, option) // [!code ++]\n    break // [!code ++]\n  // .\n  // .\n  // .\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context\n  const { params, returns, newline } = node\n\n  push(`(`, node)\n  if (isArray(params)) {\n    genNodeList(params, context, option)\n  } else if (params) {\n    genNode(params, context, option)\n  }\n  push(`) => `)\n  if (newline) {\n    push(`{`)\n    indent()\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `)\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option)\n    } else {\n      genNode(returns, context, option)\n    }\n  }\n  if (newline) {\n    deindent()\n    push(`}`)\n  }\n}\n```\n\nThere is nothing particularly difficult. That's the end of it.\n\n## Implementation of transformer\n\n### Preparation\n\nBefore implementing the transformer, there are also some preparations.\n\nAs we did with `v-on`, in the case of `v-for`, the timing to execute `processExpression` is a bit special (we need to collect local variables), so we skip it in `transformExpression`.\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (\n        dir.type === NodeTypes.DIRECTIVE &&\n        dir.name !== 'for' // [!code ++]\n      ) {\n        // .\n        // .\n        // .\n      }\n    }\n  }\n}\n```\n\n### Collecting Identifiers\n\nNow, let's think about how to collect identifiers before we move on to the main implementation.\n\nThis time, we need to consider not only simple identifiers like `fruit`, but also destructuring assignments like `{ id, name, color }`.\nFor this purpose, it seems that we need to use TreeWalker as usual.\n\nCurrently, in the `processExpression` function, the implementation is to search for identifiers and add `_ctx` to them. However, this time we only need to collect identifiers without adding anything. Let's achieve this.\n\nFirst, let's prepare a place to store the collected identifiers. Since it would be convenient for codegen and other purposes if each Node has them, let's add a property to the AST that can hold multiple identifiers on each Node.\n\nThe targets are `CompoundExpressionNode` and `SimpleExpressionNode`.\n\nSimple identifiers like `fruit` will be added to `SimpleExpressionNode`,\nand destructuring assignments like `{ id, name, color }` will be added to `CompoundExpressionNode`. (In terms of visualization, it will be a compound expression like `[\"{\", simpleExpr(\"id\"), \",\", simpleExpr(\"name\"), \",\", simpleExpr(\"color\"), \"}\"]`)\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[] // [!code ++]\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n  identifiers?: string[] // [!code ++]\n}\n```\n\nIn the `processExpression` function, let's implement the logic to collect identifiers here and skip adding prefixes by adding the collected identifiers to the transformer's context.\n\nCurrently, the functions for adding/removing identifiers are configured to receive a single identifier as a string, so let's change it to a form that assumes `{ identifier: string[] }`.\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  addIdentifiers(exp: ExpressionNode | string): void\n  removeIdentifiers(exp: ExpressionNode | string): void\n  // .\n  // .\n  // .\n}\n\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        addId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(addId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        addId(exp.content)\n      }\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        removeId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(removeId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        removeId(exp.content)\n      }\n    }\n  },\n  // .\n  // .\n  // .\n}\n```\n\nNow, let's implement the logic to collect identifiers in the `processExpression` function.\n\nIn the `processExpression` function, define an option called `asParams`, and if it is set to true, implement the logic to skip adding prefixes and collect identifiers in `node.identifiers`.\n\n`asParams` is intended to refer to the arguments (local variables) defined in the callback function of `renderList`.\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false, // [!code ++]\n) {\n  // .\n  if (isSimpleIdentifier(rawExp)) {\n    const isScopeVarReference = ctx.identifiers[rawExp]\n    if (\n      !asParams && // [!code ++]\n      !isScopeVarReference\n    ) {\n      node.content = rewriteIdentifier(rawExp)\n    } // [!code ++]\n    return node\n\n    // .\n  }\n}\n```\n\nThis is the end for simple identifiers. The problem lies in other cases.\n\nFor this, we will use `walkIdentifiers` implemented in `babelUtils`.\n\nSince we assume local variables defined as function arguments, we will convert them to \"function arguments\" in this function, and in `walkIdentifier`, we will search for them as Function params.\n\n```ts\n// Convert asParams like function arguments\nconst source = `(${rawExp})${asParams ? `=>{}` : ``}`\n\n// walkIdentifiers is slightly more complex.\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  // .\n\n  ;(walk as any)(root, {\n    // prettier-ignore\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n        \n      } else if (isFunctionType(node)) {\n        // Explained later (collecting identifiers in knownIds within this function)\n        walkFunctionParams(node, (id) =>\n          markScopeIdentifier(node, id, knownIds)\n        );\n      }\n    },\n  })\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)\n}\n```\n\nWhat we are doing here is simply walking the arguments if node is a function and collecting identifiers into `identifiers`.\n\nIn the caller of `walkIdentifiers`, we define `knownIds` and pass it to `walkIdentifiers` along with `knownIds` to collect identifiers.\n\nAfter collecting in `walkIdentifiers`, finally, we generate identifiers based on `knownIds` when generating CompoundExpression.\n\n```ts\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers)\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // pass\n  parentStack,\n)\n\n// .\n// .\n// .\n\nret.identifiers = Object.keys(knownIds) // generate identifiers based on knownIds\nreturn ret\n```\n\nAlthough the file is a bit out of order, `walkFunctionParams` and `markScopeIdentifier` simply walk through the parameters and add `Node.name` to `knownIds`.\n\n```ts\nexport function walkFunctionParams(\n  node: Function,\n  onIdent: (id: Identifier) => void,\n) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id)\n    }\n  }\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return\n  }\n  if (name in knownIds) {\n    knownIds[name]++\n  } else {\n    knownIds[name] = 1\n  }\n  ;(node.scopeIds || (node.scopeIds = new Set())).add(name)\n}\n```\n\nWith this, we should be able to collect identifiers. Let's implement `transformFor` using this and complete the v-for directive!\n\n### transformFor\n\nNow that we have overcome the hurdle, let's implement the transformer using what we have as usual.\nJust a little more, let's do our best!\n\nLike v-if, this also involves the structure, so let's implement it using `createStructuralDirectiveTransform`.\n\nI think it would be easier to understand if I write an explanation with code, so I will provide the code with explanations below. However, please try to implement it yourself by reading the source code before looking at this!\n\n```ts\n// This is the implementation of the main structure, similar to v-if.\n// It executes processFor at the appropriate place and generates codegenNode at the appropriate place.\n// processFor is the most complex implementation.\nexport const transformFor = createStructuralDirectiveTransform(\n  'for',\n  (node, dir, context) => {\n    return processFor(node, dir, context, forNode => {\n      // As expected, generate code to call renderList.\n      const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n        forNode.source,\n      ]) as ForRenderListExpression\n\n      // Generate codegenNode for the Fragment that serves as the container for v-for.\n      forNode.codegenNode = createVNodeCall(\n        context,\n        context.helper(FRAGMENT),\n        undefined,\n        renderExp,\n      ) as ForCodegenNode\n\n      // codegen process (executed after parse and identifier collection in processFor)\n      return () => {\n        const { children } = forNode\n        const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall\n\n        renderExp.arguments.push(\n          createFunctionExpression(\n            createForLoopParams(forNode.parseResult),\n            childBlock,\n            true /* force newline */,\n          ) as ForIteratorExpression,\n        )\n      }\n    })\n  },\n)\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  // Parse the expression of v-for.\n  // At the parseResult stage, identifiers of each Node have already been collected.\n  const parseResult = parseForExpression(\n    dir.exp as SimpleExpressionNode,\n    context,\n  )\n\n  const { addIdentifiers, removeIdentifiers } = context\n\n  const { source, value, key, index } = parseResult!\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  }\n\n  // Replace the Node with forNode.\n  context.replaceNode(forNode)\n\n  if (!context.isBrowser) {\n    // Add the collected identifiers to the context.\n    value && addIdentifiers(value)\n    key && addIdentifiers(key)\n    index && addIdentifiers(index)\n  }\n\n  // Generate code (this allows skipping the addition of the prefix to local variables)\n  const onExit = processCodegen && processCodegen(forNode)\n\n  return () => {\n    value && removeIdentifiers(value)\n    key && removeIdentifiers(key)\n    index && removeIdentifiers(index)\n\n    if (onExit) onExit()\n  }\n}\n\n// Parse the expression given to v-for using regular expressions.\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/\nconst stripParensRE = /^\\(|\\)$/g\n\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc\n  const exp = input.content\n  const inMatch = exp.match(forAliasRE)\n\n  if (!inMatch) return\n\n  const [, LHS, RHS] = inMatch\n  const result: ForParseResult = {\n    source: createAliasExpression(\n      loc,\n      RHS.trim(),\n      exp.indexOf(RHS, LHS.length),\n    ),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  }\n\n  if (!context.isBrowser) {\n    result.source = processExpression(\n      result.source as SimpleExpressionNode,\n      context,\n    )\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, '').trim()\n  const iteratorMatch = valueContent.match(forIteratorRE)\n  const trimmedOffset = LHS.indexOf(valueContent)\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, '').trim()\n    const keyContent = iteratorMatch[1].trim()\n    let keyOffset: number | undefined\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)\n      result.key = createAliasExpression(loc, keyContent, keyOffset)\n      if (!context.isBrowser) {\n        // If not in browser mode, set asParams to true and collect identifiers of key.\n        result.key = processExpression(result.key, context, true)\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim()\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key\n              ? keyOffset! + keyContent.length\n              : trimmedOffset + valueContent.length,\n          ),\n        )\n        if (!context.isBrowser) {\n          // If not in browser mode, set asParams to true and collect identifiers of index.\n          result.index = processExpression(result.index, context, true)\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset)\n    if (!context.isBrowser) {\n      // If not in browser mode, set asParams to true and collect identifiers of value.\n      result.value = processExpression(result.value, context, true)\n    }\n  }\n\n  return result\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(\n    content,\n    false,\n    getInnerRange(range, offset, content.length),\n  )\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs])\n}\n\nfunction createParamsList(\n  args: (ExpressionNode | undefined)[],\n): ExpressionNode[] {\n  let i = args.length\n  while (i--) {\n    if (args[i]) break\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))\n}\n```\n\nNow, the remaining part is the implementation of renderList that is actually included in the compiled code, and the implementation of registering the transformer. If we can implement these, v-for should work!\n\nLet's try running it!\n\n![v-for result in the browser](/figures/50-basic-template-compiler/v-for/v-for-result.png)\n\nIt seems to be going well.\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/050_v_for)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/070-resolve-component.md",
    "content": "# Resolving Components\n\nActually, our chibivue template cannot resolve components yet.\nLet's implement it here, as Vue.js provides several ways to resolve components.\n\nFirst, let's review some of the resolution methods.\n\n## Resolution Methods for Components\n\n### 1. Components Option (Local Registration)\n\nThis is probably the simplest way to resolve components.\n\nhttps://vuejs.org/api/options-misc.html#components\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: {\n    MyComponent,\n    MyComponent2: MyComponent,\n  },\n}\n</script>\n\n<template>\n  <MyComponent />\n  <MyComponent2 />\n</template>\n```\n\nThe key names specified in the components option object become the component names that can be used in the template.\n\n### 2. Registering on the app (Global Registration)\n\nYou can register components that can be used throughout the application by using the `.component()` method of the created Vue application.\n\nhttps://vuejs.org/guide/components/registration.html#global-registration\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({})\n\napp\n  .component('ComponentA', ComponentA)\n  .component('ComponentB', ComponentB)\n  .component('ComponentC', ComponentC)\n```\n\n### 3. Dynamic Components + is Attribute\n\nBy using the is attribute, you can dynamically switch components.\n\nhttps://vuejs.org/api/built-in-special-elements.html#component\n\n```vue\n<script>\nimport Foo from './Foo.vue'\nimport Bar from './Bar.vue'\n\nexport default {\n  components: { Foo, Bar },\n  data() {\n    return {\n      view: 'Foo',\n    }\n  },\n}\n</script>\n\n<template>\n  <component :is=\"view\" />\n</template>\n```\n\n### 4. Importing during script setup\n\nIn script setup, you can directly use the imported components.\n\n```vue\n<script setup>\nimport MyComponent from './MyComponent.vue'\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\nIn addition, there are also asynchronous components, embedded components, and the `component` tag, but this time I will try to handle the above two (1, 2).\n\nRegarding 3, if 1 and 2 can handle it, it is just an extension. As for 4, since script setup has not been implemented yet, we will put it off for a while.\n\n## Basic Approach\n\nThe basic approach to resolving components is as follows:\n\n- Somewhere, store the names and component records used in the template.\n- Use helper functions to resolve components based on their names.\n\nBoth the form of 1 and the form of 2 simply store the names and component records, with the only difference being where they are registered.  \nIf you have the records, you can resolve the components from the names when necessary, so both implementations will be similar.\n\nFirst, let's take a look at the expected code and the compilation result.\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default defineComponent({\n  components: { MyComponent },\n})\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n```js\n// Compilation result\n\nfunction render(_ctx) {\n  const {\n    resolveComponent: _resolveComponent,\n    createVNode: _createVNode,\n    Fragment: _Fragment,\n  } = ChibiVue\n\n  const _component_MyComponent = _resolveComponent('MyComponent')\n\n  return _createVNode(_Fragment, null, _createVNode(_component_MyComponent))\n}\n```\n\nIt looks like this.\n\n## Implementation\n\n### AST\n\nIn order to generate code that resolves components, we need to know that \"MyComponent\" is a component.  \nAt the parse stage, we handle the tag name and separate it into a regular Element and a Component on the AST.\n\nFirst, let's consider the definition of the AST.  \nThe ComponentNode, like a regular Element, has props and children.  \nWhile consolidating these common parts as `BaseElementNode`, we will rename the existing `ElementNode` to `PlainElementNode`,  \nand make `ElementNode` a union of `PlainElementNode` and `ComponentNode`.\n\n```ts\n// compiler-core/ast.ts\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  tagType: ElementTypes\n  isSelfClosing: boolean\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT\n  codegenNode: VNodeCall | undefined\n}\n```\n\nThe content is the same as before, but we distinguish them by `tagType` and treat them as separate ASTs.  \nWe will use this in the transform phase to add helper functions, etc.\n\n### Parser\n\nNext, let's implement the parser to generate the above AST.  \nBasically, we just need to determine the `tagType` based on the tag name.\n\nThe problem is how to determine whether it is an Element or a Component.\n\nThe basic idea is simple: just determine whether it is a \"native tag\" or not.\n\n・  \n・  \n・\n\n\"Wait, wait, that's not what I'm asking. How do we actually implement it?\"\n\nYes, this is a brute force approach. We predefine a list of native tag names and determine whether it matches or not.  \nAs for what items should be enumerated, all of them should be written in the specification, so we will trust it and use it.\n\nOne problem, if any, is that \"what is a native tag\" can vary depending on the environment.  \nIn this case, it's the browser. What I mean is that \"compiler-core should not depend on the environment\".  \nWe have implemented such DOM-dependent implementations in compiler-dom so far, and this enumeration is no exception.\n\nWith that in mind, we will implement it so that the function \"whether it is a native tag or not\" can be injected as an option from outside the parser, considering future possibilities and making it easy to add various options later.\n\n```ts\ntype OptionalOptions = 'isNativeTag' // | TODO: Add more in the future (maybe)\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>\n\nexport interface ParserContext {\n  // .\n  // .\n  options: MergedParserOptions // [!code ++]\n  // .\n  // .\n}\n\nfunction createParserContext(\n  content: string,\n  rawOptions: ParserOptions, // [!code ++]\n): ParserContext {\n  const options = Object.assign({}, defaultParserOptions) // [!code ++]\n\n  let key: keyof ParserOptions // [!code ++]\n  // prettier-ignore\n  for (key in rawOptions) { // [!code ++]\n    options[key] = // [!code ++]\n      rawOptions[key] === undefined // [!code ++]\n        ? defaultParserOptions[key] // [!code ++]\n        : rawOptions[key]; // [!code ++]\n  } // [!code ++]\n\n  // .\n  // .\n  // .\n}\n\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {}, // [!code ++]\n): RootNode => {\n  const context = createParserContext(\n    content,\n    options, // [!code ++]\n  )\n  const children = parseChildren(context, [])\n  return createRoot(children)\n}\n```\n\nNow, in the compiler-dom, we will enumerate the native tag names and pass them as options.\n\nAlthough I mentioned compiler-dom, the enumeration itself is done in shared/domTagConfig.ts.\n\n```ts\nimport { makeMap } from './makeMap'\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +\n  'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +\n  'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +\n  'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +\n  'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +\n  'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +\n  'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +\n  'option,output,progress,select,textarea,details,dialog,menu,' +\n  'summary,template,blockquote,iframe,tfoot'\n\nexport const isHTMLTag = makeMap(HTML_TAGS)\n```\n\nIt looks quite ominous, doesn't it?\n\nBut this is the correct implementation.\n\nhttps://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/shared/src/domTagConfig.ts#L6\n\nCreate compiler-dom/parserOptions.ts and pass it to the compiler.\n\n```ts\n// compiler-dom/parserOptions.ts\n\nimport { ParserOptions } from '../compiler-core'\nimport { isHTMLTag, isSVGTag } from '../shared/domTagConfig'\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),\n}\n```\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(\n    template,\n    Object.assign(\n      {},\n      parserOptions, // [!code ++]\n      defaultOption,\n      {\n        directiveTransforms: DOMDirectiveTransforms,\n      },\n    ),\n  )\n}\n```\n\nThe implementation of the parser is complete, so we will now proceed to implement the remaining parts.\n\nThe remaining part is very simple. We just need to determine whether it is a component or not and assign a tagType.\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // .\n  // .\n  let tagType = ElementTypes.ELEMENT // [!code ++]\n  // prettier-ignore\n  if (isComponent(tag, context)) { // [!code ++]\n    tagType = ElementTypes.COMPONENT;// [!code ++]\n  } // [!code ++]\n\n  return {\n    // .\n    tagType, // [!code ++]\n    // .\n  }\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options\n  if (\n    // NOTE: In Vue.js, tags starting with uppercase letters are treated as components.\n    // ref: https://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/compiler-core/src/parse.ts#L662\n    /^[A-Z]/.test(tag) ||\n    (options.isNativeTag && !options.isNativeTag(tag))\n  ) {\n    return true\n  }\n}\n```\n\nWith this, the parser and AST are complete. We will now proceed to implement the transform and codegen using these.\n\n### Transform\n\nWhat needs to be done in the transform is very simple.\n\nIn transformElement, we just need to make a slight conversion if the Node is a ComponentNode.\n\nAt this time, we also register the component in the context.  \nThis is done so that we can resolve it collectively during codegen.\nAs mentioned later, components will be resolved collectively as assets in codegen.\n\n```ts\n// compiler-core/transforms/transformElement.ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    // .\n    // .\n\n    const isComponent = node.tagType === ElementTypes.COMPONENT // [!code ++]\n\n    const vnodeTag = isComponent // [!code ++]\n      ? resolveComponentType(node as ComponentNode, context) // [!code ++]\n      : `\"${tag}\"` // [!code ++]\n\n    // .\n    // .\n  }\n}\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node\n  context.helper(RESOLVE_COMPONENT)\n  context.components.add(tag) // explained later\n  return toValidAssetId(tag, `component`)\n}\n```\n\n```ts\n// util.ts\nexport function toValidAssetId(\n  name: string,\n  type: 'component', // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === '-' ? '_' : name.charCodeAt(replaceValue).toString()\n  })}`\n}\n```\n\nWe also make sure to register it in the context.\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  components: Set<string> // [!code ++]\n  // .\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  {\n    nodeTransforms = [],\n    directiveTransforms = {},\n    isBrowser = false,\n  }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    components: new Set(), // [!code ++]\n    // .\n  }\n}\n```\n\nAnd then, all the components in the context are registered in the RootNode of the target components.\n\n```ts\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT\n  children: TemplateChildNode[]\n  codegenNode?: TemplateChildNode | VNodeCall\n  helpers: Set<symbol>\n  components: string[] // [!code ++]\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  createRootCodegen(root, context)\n  root.helpers = new Set([...context.helpers.keys()])\n  root.components = [...context.components] // [!code ++]\n}\n```\n\nWith this, all that's left is to use RootNode.components in codegen.\n\n### Codegen\n\nThe code simply generates code by passing the name to helper functions to resolve, just like the compilation result we saw at the beginning. We are abstracting it as \"assets\" for future considerations.\n\n```ts\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  // .\n  // .\n  genFunctionPreamble(ast, context) // NOTE: Move this outside the function in the future\n\n  // prettier-ignore\n  if (ast.components.length) { // [!code ++]\n    genAssets(ast.components, \"component\", context); // [!code ++]\n    newline(); // [!code ++]\n    newline(); // [!code ++]\n  } // [!code ++]\n\n  push(`return `)\n  // .\n  // .\n}\n\nfunction genAssets(\n  assets: string[],\n  type: 'component' /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === 'component') {\n    const resolver = helper(RESOLVE_COMPONENT)\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i]\n\n      push(\n        `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(\n          id,\n        )})`,\n      )\n      if (i < assets.length - 1) {\n        newline()\n      }\n    }\n  }\n}\n```\n\n### Implementation on the runtime-core side\n\nNow that we have generated the desired code, let's move on to the implementation in runtime-core.\n\n#### Adding \"component\" as an option for components\n\nThis is simple, just add it to the options.\n\n```ts\nexport type ComponentOptions<\n  // .\n  // .\n> = {\n  // .\n  components?: Record<string, Component>\n  // .\n}\n```\n\n#### Adding \"components\" as an option for the app\n\nThis is also simple.\n\n```ts\nexport interface AppContext {\n  // .\n  components: Record<string, Component> // [!code ++]\n  // .\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    // .\n    components: {}, // [!code ++]\n    // .\n  }\n}\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    // .\n    const app: App = (context.app = {\n      // .\n      // prettier-ignore\n      component(name: string, component: Component): any { // [!code ++]\n        context.components[name] = component; // [!code ++]\n        return app; // [!code ++]\n      },\n    })\n  }\n}\n```\n\n#### Implementing a function to resolve components from the above two\n\nThere is nothing special to explain here.  \nIt searches for components registered locally and globally, and returns the component.  \nIf it is not found, it returns the name as is as a fallback.\n\n```ts\n// runtime-core/helpers/componentAssets.ts\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance // explained later\n  if (instance) {\n    const Component = instance.type\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name)\n    return res\n  }\n\n  return name\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry &&\n    (registry[name] ||\n      registry[camelize(name)] ||\n      registry[capitalize(camelize(name))])\n  )\n}\n```\n\nOne thing to note is `currentRenderingInstance`.\n\nIn order to traverse locally registered components in `resolveComponent`, we need to access the currently rendering component.  \n(We want to search the `components` option of the component being rendered)\n\nWith that in mind, let's prepare `currentRenderingInstance` and update it when rendering.\n\n```ts\n// runtime-core/componentRenderContexts.ts\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance\n  currentRenderingInstance = instance\n  return prev\n}\n```\n\n```ts\n// runtime-core/renderer.ts\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const componentUpdateFn = () => {\n    // .\n    // .\n    const prev = setCurrentRenderingInstance(instance) // [!code ++]\n    const subTree = (instance.subTree = normalizeVNode(render(proxy!))) // [!code ++]\n    setCurrentRenderingInstance(prev) // [!code ++]\n    // .\n    // .\n  }\n  // .\n  // .\n}\n```\n\n## Let's try it out\n\nGreat job! We can finally resolve components.\n\nLet's try running it in the playground!\n\n```ts\nimport { createApp } from 'chibivue'\n\nimport App from './App.vue'\nimport Counter from './components/Counter.vue'\n\nconst app = createApp(App)\napp.component('GlobalCounter', Counter)\napp.mount('#app')\n```\n\nApp.vue\n\n```vue\n<script>\nimport Counter from './components/Counter.vue'\n\nimport { defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  components: { Counter },\n})\n</script>\n\n<template>\n  <Counter />\n  <Counter />\n  <GlobalCounter />\n</template>\n```\n\ncomponents/Counter.vue\n\n```vue\n<script>\nimport { ref, defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n})\n</script>\n\n<template>\n  <button @click=\"count++\">count: {{ count }}</button>\n</template>\n```\n\n![resolveComponent result in the browser](/figures/50-basic-template-compiler/resolve-component/resolve-components-result.png)\n\nIt seems to be working fine! Great job!\n\nSource code up to this point: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/060_resolve_components)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/080-component-slot-outlet.md",
    "content": "# Component Slots\n\n## Desired Developer Interface\n\nWe already have the runtime implementation for the slot implementation of the Basic Component System.\\\nHowever, we still cannot handle slots in templates.\n\nWe want to handle SFCs like the following:\\\n(Although we say SFC, it is actually the implementation of the template compiler.)\n\n```vue\n<!-- Comp.vue -->\n<template>\n  <p><slot name=\"default\" /></p>\n</template>\n```\n\n```vue\n<!-- App.vue -->\n<script>\nimport Comp from './Comp.vue'\nexport default {\n  components: {\n    Comp,\n  },\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n}\n</script>\n\n<template>\n  <Comp>\n    <template #default>\n      <button @click=\"count++\">count is: {{ count }}</button>\n    </template>\n  </Comp>\n</template>\n```\n\nThere are several types of slots in Vue.js:\n\n- Default slots\n- Named slots\n- Scoped slots\n\nHowever, as you may already understand from the runtime implementation, these are all just callback functions. Let's review them just in case.\n\nComponents like the ones above are transformed into render functions as follows.\n\n```js\nh(Comp, null, {\n  default: () =>\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n})\n\n```\n\nIn the template, the `name=\"default\"` attribute can be omitted, but at runtime, it will still be treated as a slot named `default`. We will implement the compiler for default slots after completing the implementation for named slots.\n\n## Implementing the Compiler (Slot Definition)\n\nAs usual, we will implement the parsing and code generation processes, but this time we will handle both the slot definition and slot insertion.\n\nFirst, let's focus on the slot definition. This is the part that is represented as `<slot name=\"my-slot\"/>` on the child component side.\n\nIn the runtime, we will prepare a helper function called `renderSlot`, which will take the slots inserted through the component instance (via `ctx.$slot`) and their names as arguments. The source code will be compiled into something like the following:\n\n```js\n_renderSlot(_ctx.$slots, \"my-slot\")\n```\n\nWe will represent the slot definition as a node called `SlotOutletNode` in the AST.\\\nAdd the following definition to `ast.ts`.\n\n```ts\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT, // [!code ++]\n}\n\n// ...\n\nexport type ElementNode = \n  | PlainElementNode \n  | ComponentNode \n  | SlotOutletNode // [!code ++]\n\n// ...\n\nexport interface SlotOutletNode extends BaseElementNode { // [!code ++]\n  tagType: ElementTypes.SLOT // [!code ++]\n  codegenNode: RenderSlotCall | undefined // [!code ++]\n} // [!code ++]\n\nexport interface RenderSlotCall extends CallExpression { // [!code ++]\n  callee: typeof RENDER_SLOT // [!code ++]\n  // $slots, name // [!code ++]\n  arguments: [string, string | ExpressionNode] // [!code ++]\n} // [!code ++]\n```\n\nLet's write the parsing process to generate this AST.\n\nIn `parse.ts`, the task is simple: when parsing the tag, if it is `\"slot\"`, change it to `ElementTypes.SLOT`.\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // ...\n  let tagType = ElementTypes.ELEMENT\n  if (tag === 'slot') { // [!code ++]\n    tagType = ElementTypes.SLOT // [!code ++]\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT\n  }\n}\n```\n\nNow that we have reached this point, the next step is to implement the transformer to generate the `codegenNode`.\\\nWe need to create a `JS_CALL_EXPRESSION` for the helper function.\n\nAs a preliminary step, add `RENDER_SLOT` to `runtimeHelper.ts`.\n\n```ts\n// ...\nexport const RENDER_LIST = Symbol()\nexport const RENDER_SLOT = Symbol() // [!code ++]\nexport const MERGE_PROPS = Symbol()\n// ...\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: 'renderSlot', // [!code ++]\n  [MERGE_PROPS]: 'mergeProps',\n  // ...\n}\n```\n\nWe will implement a new transformer called `transformSlotOutlet`.\\\nThe task is very simple: when the `ElementType.SLOT` is encountered, we search for the `name` in `node.props` and generate a `JS_CALL_EXPRESSION` for `RENDER_SLOT`.\\\nWe also consider cases where the name is bound, such as `:name=\"slotName\"`.\n\nSince it is straightforward, here is the complete transformer code (please read through it).\n\n```ts\nimport { camelize } from '../../shared'\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from '../ast'\nimport { RENDER_SLOT } from '../runtimeHelpers'\nimport type { NodeTransform, TransformContext } from '../transform'\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node\n    const { slotName } = processSlotOutlet(node, context)\n    const slotArgs: CallExpression['arguments'] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ]\n\n    node.codegenNode = createCallExpression(\n      context.helper(RENDER_SLOT),\n      slotArgs,\n      loc,\n    )\n  }\n}\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`\n\n  const nonNameProps = []\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === 'name') {\n          slotName = JSON.stringify(p.value.content)\n        } else {\n          p.name = camelize(p.name)\n          nonNameProps.push(p)\n        }\n      }\n    } else {\n      if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {\n        if (p.exp) slotName = p.exp\n      } else {\n        if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content)\n        }\n        nonNameProps.push(p)\n      }\n    }\n  }\n\n  return { slotName }\n}\n```\n\nIn the future, we will also add prop exploration for scoped slots here.\n\nOne point to note is that the `<slot />` element will also be caught by `transformElement`, so we will add an implementation to skip it when `ElementTypes.SLOT` is encountered.\n\nHere is the `transformElement.ts`.\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if ( // [!code ++]\n      !( // [!code ++]\n        node.type === NodeTypes.ELEMENT && // [!code ++]\n        (node.tagType === ElementTypes.ELEMENT || // [!code ++]\n          node.tagType === ElementTypes.COMPONENT) // [!code ++]\n      ) // [!code ++]\n    ) { // [!code ++]\n      return // [!code ++]\n    } // [!code ++]\n\n    // ...\n  }\n}\n```\nFinally, by registering `transformSlotOutlet` in `compile.ts`, the compilation should be possible.\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformIf,\n      transformFor,\n      transformExpression,\n      transformSlotOutlet, // [!code ++]\n      transformElement,\n    ],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\nWe have not yet implemented the runtime function `renderSlot`, so we will do that last to complete the implementation of the slot definition.\n\nLet's implement `packages/runtime-core/helpers/renderSlot.ts`.\n\n```ts\nimport { Fragment, type VNode, createVNode } from '../vnode'\nimport type { Slots } from '../componentSlots'\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name]\n  if (!slot) {\n    slot = () => []\n  }\n\n  return createVNode(Fragment, {}, slot())\n}\n```\n\nThe implementation of the slot definition is now complete.\\\nNext, let's implement the compiler for the slot insertion side!\n\nSource code up to this point:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/080_component_slot_outlet)\n\n## Slot Insertion\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/080-slot.md",
    "content": "---\nwip: true\n---\n\n# Slots\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/085-component-slot-insert.md",
    "content": "# Supporting Slots (Usage)\n\n## Slot Insertion\n\nNext, let's implement the slot insertion side.\nThis is the compilation of the `<template #slot-name>` part expressed in the parent component.\n\nAs explained at the beginning, slots are compiled as follows:\n\n```js\nh(Comp, null, {\n  default: _withCtx(() => [\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n  ]),\n})\n```\n\nIn other words, the component's children are treated as `SlotsExpression` (ObjectExpression), each slot is generated as `FunctionExpression`, and wrapped with `withCtx`.\n\n## The Role of withCtx\n\n`withCtx` is a helper function that executes slot functions in the context of the correct component instance. This ensures that reactive dependencies within slots are tracked to the correct component.\n\n```ts\nexport function withCtx(\n  fn: Function,\n  ctx: ComponentInternalInstance | null = currentRenderingInstance,\n) {\n  if (!ctx) return fn;\n\n  const renderFnWithContext = (...args: any[]) => {\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    try {\n      return fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n    }\n  };\n\n  return renderFnWithContext;\n}\n```\n\n## Updating the AST\n\nFirst, let's update the AST definitions.\nWe'll add a type called `SlotsExpression` and add an `isSlot` flag to `FunctionExpression` to indicate it's a slot function.\n\n```ts\n// SlotsExpression is an ObjectExpression that represents the slots object\n// passed to a component. e.g., { default: () => [...], header: () => [...] }\nexport interface SlotsExpression extends ObjectExpression {}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n  isSlot?: boolean // [!code ++]\n}\n```\n\nAlso, add `SlotsExpression` to the `children` type of `VNodeCall`.\n\n```ts\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[]\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | SlotsExpression // [!code ++]\n    | undefined\n}\n```\n\n## Adding the Helper\n\nAdd `WITH_CTX` to `runtimeHelpers.ts`.\n\n```ts\nexport const WITH_CTX = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_CTX]: 'withCtx',\n}\n```\n\n## Adding Utility Functions\n\nAdd utility functions `findDir` and `isTemplateNode` to `utils.ts`.\n\n```ts\nexport function isTemplateNode(\n  node: RootNode | TemplateChildNode,\n): node is PlainElementNode & { tag: 'template' } {\n  return (\n    node.type === NodeTypes.ELEMENT &&\n    node.tagType === ElementTypes.ELEMENT &&\n    node.tag === 'template'\n  )\n}\n\nexport function findDir(\n  node: ElementNode,\n  name: string | RegExp,\n  allowEmpty: boolean = false,\n): DirectiveNode | undefined {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (\n      p.type === NodeTypes.DIRECTIVE &&\n      (allowEmpty || p.exp) &&\n      (typeof name === 'string' ? p.name === name : name.test(p.name))\n    ) {\n      return p\n    }\n  }\n}\n```\n\n`isTemplateNode` determines if a node is a `<template>` tag, and `findDir` finds a directive with the specified name.\n\n## Implementing buildSlots\n\nImplement the `buildSlots` function in `transforms/vSlot.ts` to handle slot insertion.\n\n```ts\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type Property,\n  type SlotsExpression,\n  type TemplateChildNode,\n  createCallExpression,\n  createFunctionExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from '../ast'\nimport { WITH_CTX } from '../runtimeHelpers'\nimport type { TransformContext } from '../transform'\nimport { findDir, isStaticExp, isTemplateNode } from '../utils'\n\n// Build slots object for a component\nexport function buildSlots(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  slots: SlotsExpression\n} {\n  const { children } = node\n  const slotsProperties: Property[] = []\n\n  // 1. Check for slot with slotProps on component itself.\n  //    <Comp v-slot=\"{ prop }\"/>\n  const onComponentSlot = findDir(node, 'slot', true)\n  if (onComponentSlot) {\n    const { arg, exp } = onComponentSlot\n    slotsProperties.push(\n      createObjectProperty(\n        arg || createSimpleExpression('default', true),\n        buildSlotFn(exp, children, node.loc, context),\n      ),\n    )\n  }\n\n  // 2. Iterate through children and check for template slots\n  //    <template v-slot:foo=\"{ prop }\">\n  let hasTemplateSlots = false\n  const implicitDefaultChildren: TemplateChildNode[] = []\n\n  for (let i = 0; i < children.length; i++) {\n    const slotElement = children[i]\n    let slotDir: DirectiveNode | undefined\n\n    if (\n      !isTemplateNode(slotElement) ||\n      !(slotDir = findDir(slotElement, 'slot', true))\n    ) {\n      // not a <template v-slot>, skip.\n      if (slotElement.type !== NodeTypes.COMMENT) {\n        implicitDefaultChildren.push(slotElement)\n      }\n      continue\n    }\n\n    hasTemplateSlots = true\n    const { children: slotChildren, loc: slotLoc } = slotElement\n    const {\n      arg: slotName = createSimpleExpression(`default`, true),\n      exp: slotProps,\n    } = slotDir\n\n    const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc, context)\n    slotsProperties.push(createObjectProperty(slotName, slotFunction))\n  }\n\n  if (!onComponentSlot) {\n    if (!hasTemplateSlots) {\n      // implicit default slot (on component)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, children, node.loc, context),\n        ),\n      )\n    } else if (implicitDefaultChildren.length) {\n      // implicit default slot (mixed with named slots)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, implicitDefaultChildren, node.loc, context),\n        ),\n      )\n    }\n  }\n\n  const slots = createObjectExpression(\n    slotsProperties,\n    node.loc,\n  ) as SlotsExpression\n\n  return {\n    slots,\n  }\n}\n\nfunction buildSlotFn(\n  props: ExpressionNode | undefined,\n  children: TemplateChildNode[],\n  loc: any,\n  context: TransformContext,\n) {\n  const fn = createFunctionExpression(\n    props,\n    children,\n    false /* newline */,\n    children.length ? children[0].loc : loc,\n  )\n  fn.isSlot = true\n  return createCallExpression(context.helper(WITH_CTX), [fn], loc)\n}\n```\n\nThe `buildSlots` function handles three patterns:\n\n1. **When v-slot is on the component itself** (`<Comp v-slot=\"{ prop }\"/>`)\n2. **When defining named slots with template tags** (`<template #foo>`)\n3. **Implicit default slot** (child elements when there are no named slots)\n\n## Updating transformElement\n\nFinally, update `transformElement.ts` to process component children with `buildSlots`.\n\n```ts\nimport { buildSlots } from './vSlot'\n\n// ...\n\n// children\nif (node.children.length > 0) {\n  if (isComponent) {\n    // For components, build slots object // [!code ++]\n    const { slots } = buildSlots(node, context) // [!code ++]\n    vnodeChildren = slots as SlotsExpression // [!code ++]\n  } else if (node.children.length === 1) {\n    const child = node.children[0]\n    const type = child.type\n    const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n    if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n      vnodeChildren = child as TemplateTextChildNode\n    } else {\n      vnodeChildren = node.children\n    }\n  } else {\n    vnodeChildren = node.children\n  }\n}\n```\n\nThis completes the slot insertion compilation.\nComponent children are automatically converted to slot objects, generating code like this:\n\n```vue\n<Comp>\n  <template #header>\n    <h1>Header</h1>\n  </template>\n  <template #default>\n    <p>Content</p>\n  </template>\n</Comp>\n```\n\n↓\n\n```js\n_createVNode(_component_Comp, null, {\n  header: _withCtx(() => [_createVNode('h1', null, 'Header')]),\n  default: _withCtx(() => [_createVNode('p', null, 'Content')]),\n})\n```\n\nThis completes the basic slot compiler implementation!\n\nSource code up to this point:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/085_component_slot_insert)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/090-other-directives.md",
    "content": "# Other Directives\n\nSo far, we have implemented major directives like v-bind, v-on, v-if, v-for, and v-model.\\\nIn this chapter, we will implement the remaining built-in directives.\n\nThe directives we will implement are:\n\n- v-text\n- v-html\n- v-cloak\n- v-pre\n\nFor v-show, since it requires a runtime directive mechanism, we will cover it in the Custom Directives chapter.\\\nAlso, v-once and v-memo are related to optimization, so they will be covered in the Optimizations section of Web Application Essentials.\n\n## v-text\n\n### Target Developer Interface\n\nv-text is a directive that updates the element's textContent.\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello!')\n    return { msg }\n  },\n}\n</script>\n\n<template>\n  <span v-text=\"msg\"></span>\n  <!-- Same as below -->\n  <span>{{ msg }}</span>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-text\n\n### Implementation Approach\n\nThe implementation of v-text is very simple.\\\nAt compile time, we just transform the v-text directive into a `textContent` property binding.\n\n```html\n<span v-text=\"msg\"></span>\n```\n\n↓\n\n```ts\nh('span', { textContent: msg })\n```\n\n### Implementing the Transformer in compiler-dom\n\nSince v-text is a DOM-specific directive, we implement it in compiler-dom.\n\nCreate `packages/compiler-dom/src/transforms/vText.ts`.\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-text is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-text will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\nThe key points are:\n\n- Output an error if exp doesn't exist\n- Output a warning and clear children if they exist (since v-text overrides children)\n- Bind exp as a `textContent` property\n\nThen register the transformer in `packages/compiler-dom/src/index.ts`.\n\n```ts\nimport { transformVText } from './transforms/vText'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText, // [!code ++]\n}\n```\n\nThat completes the v-text implementation!\n\n## v-html\n\n### Target Developer Interface\n\nv-html is a directive that updates the element's innerHTML.\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const rawHtml = ref('<span style=\"color: red\">This should be red.</span>')\n    return { rawHtml }\n  },\n}\n</script>\n\n<template>\n  <p>Using v-html directive: <span v-html=\"rawHtml\"></span></p>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-html\n\n::: warning\nSince v-html directly manipulates innerHTML, it can be a source of XSS vulnerabilities.\\\nAvoid displaying untrusted user input with v-html.\n:::\n\n### Implementation Approach\n\nLike v-text, v-html is transformed into an `innerHTML` property binding at compile time.\n\n```html\n<span v-html=\"rawHtml\"></span>\n```\n\n↓\n\n```ts\nh('span', { innerHTML: rawHtml })\n```\n\n### Implementing the Transformer in compiler-dom\n\nCreate `packages/compiler-dom/src/transforms/vHtml.ts`.\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-html is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-html will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\nIt has almost the same structure as v-text. The only difference is using `innerHTML` instead of `textContent`.\n\nRegister the transformer in `packages/compiler-dom/src/index.ts`.\n\n```ts\nimport { transformVHtml } from './transforms/vHtml'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText,\n  html: transformVHtml, // [!code ++]\n}\n```\n\nThat completes the v-html implementation!\n\n## v-cloak\n\n### Target Developer Interface\n\nv-cloak is a directive used to hide an element until the component is mounted.\\\nIt's used in combination with CSS to prevent users from seeing uncompiled template syntax (like mustaches).\n\n```css\n[v-cloak] {\n  display: none;\n}\n```\n\n```text\n<div v-cloak>\n  ｛｛ message ｝｝\n</div>\n```\n\nAfter mounting, the v-cloak attribute is automatically removed.\n\nhttps://vuejs.org/api/built-in-directives.html#v-cloak\n\n### Implementation Approach\n\nThe implementation of v-cloak is very simple.\\\nWe just need to remove the v-cloak attribute from the element when mounting.\n\nThis is processed on the runtime side, not in the compiler.\\\nSpecifically, we add processing in the `mountElement` function in `renderer.ts`.\n\n### Implementing in Runtime\n\nAdd the following processing to the `mountElement` function in `packages/runtime-core/src/renderer.ts`.\n\n```ts\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  let el: RendererElement\n  const { type, props, children, shapeFlag } = vnode\n\n  el = vnode.el = hostCreateElement(type as string)\n\n  // ... existing processing ...\n\n  // Remove v-cloak // [!code ++]\n  if (props && 'v-cloak' in props) { // [!code ++]\n    delete (el as any)['v-cloak'] // [!code ++]\n    hostRemoveAttribute(el, 'v-cloak') // [!code ++]\n  } // [!code ++]\n\n  hostInsert(el, container, anchor)\n\n  // ... existing processing ...\n}\n```\n\nWhile we could use existing `hostPatchProp` to implement `hostRemoveAttribute`, let's simply add it to `nodeOps`.\n\nAdd to `packages/runtime-dom/src/nodeOps.ts`.\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // ... existing processing ...\n  removeAttribute: (el, key) => {\n    el.removeAttribute(key)\n  },\n}\n```\n\nAlso add to the `RendererOptions` type in `packages/runtime-core/src/renderer.ts`.\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement,\n> {\n  // ... existing processing ...\n  removeAttribute(el: HostElement, key: string): void\n}\n```\n\nThat completes the v-cloak implementation!\n\n## v-pre\n\n### Target Developer Interface\n\nv-pre is a directive that skips compilation for this element and all its children.\\\nIt's used when you want to display mustache syntax as-is.\n\n```text\n<template>\n  <span v-pre>｛｛ this will not be compiled ｝｝</span>\n</template>\n```\n\nThe template above will display the text `｛｛ this will not be compiled ｝｝` as-is.\n\nhttps://vuejs.org/api/built-in-directives.html#v-pre\n\n### Implementation Approach\n\nUnlike other directives, v-pre is processed at the parser stage.\\\nWhen we detect an element with the v-pre attribute, we skip parsing of directives and mustache syntax for that element and its children.\n\n### Implementing in Parser\n\nAdd v-pre processing to `packages/compiler-core/src/parse.ts`.\n\nFirst, add an `inVPre` flag to the parser context.\n\n```ts\nexport interface ParserContext {\n  // ... existing properties ...\n  inVPre: boolean // [!code ++]\n}\n\nfunction createParserContext(content: string, options: ParserOptions): ParserContext {\n  return {\n    // ... existing processing ...\n    inVPre: false, // [!code ++]\n  }\n}\n```\n\nNext, check for the v-pre attribute when parsing elements, and set `inVPre` to true in that case.\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // Start tag\n  const element = parseTag(context, TagType.Start)\n\n  // Check for v-pre // [!code ++]\n  const isPreBoundary = element.props.some( // [!code ++]\n    p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre' // [!code ++]\n  ) // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = true // [!code ++]\n  } // [!code ++]\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = children\n\n    // End tag\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End)\n    }\n  }\n\n  // End of v-pre // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = false // [!code ++]\n  } // [!code ++]\n\n  return element\n}\n```\n\nThen, skip parsing of directives and mustache syntax when `inVPre` is true.\n\nModify the `parseAttribute` function.\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // ... attribute name parsing ...\n\n  // Don't parse as directive when in v-pre // [!code ++]\n  if (context.inVPre) { // [!code ++]\n    return { // [!code ++]\n      type: NodeTypes.ATTRIBUTE, // [!code ++]\n      name, // [!code ++]\n      value: value && { // [!code ++]\n        type: NodeTypes.TEXT, // [!code ++]\n        content: value.content, // [!code ++]\n        loc: value.loc, // [!code ++]\n      }, // [!code ++]\n      loc, // [!code ++]\n    } // [!code ++]\n  } // [!code ++]\n\n  // Directive parsing ...\n}\n```\n\nAlso modify the `parseChildren` function to skip mustache syntax parsing.\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (startsWith(s, context.options.delimiters[0])) {\n      // Skip mustache when in v-pre // [!code ++]\n      if (!context.inVPre) { // [!code ++]\n        node = parseInterpolation(context)\n      } // [!code ++]\n    } else if (s[0] === '<') {\n      // ... element parsing ...\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\nThat completes the v-pre implementation!\n\n## Checking the Behavior\n\nLet's verify that the implemented directives work correctly.\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>｛｛ msg ｝｝ will not be compiled</span>\n  </div>\n</template>\n```\n\nDid it work correctly?\\\nThis completes the implementation of basic built-in directives!\n\nv-show and custom directives will be covered in the next chapter.\\\nv-once and v-memo will be covered in the optimization chapter.\n\nSource code up to this point:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/090_other_directives)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/100-chore-compiler.md",
    "content": "# Compiler Refinements\n\nIn this chapter, we will make several adjustments to improve the quality of the template compiler.\\\nWe will cover two main topics:\n\n1. **Whitespace Handling** - Remove and condense unnecessary whitespace\n2. **Text Node Merging** - Efficiently merge adjacent text nodes\n\nThese are optimizations to improve the quality of the generated code rather than visible features.\n\n## Whitespace Handling\n\n### The Problem\n\nIn the current implementation, all whitespace in templates is preserved as-is.\\\nConsider the following template:\n\n```html\n<div>\n  <span>Hello</span>\n  <span>World</span>\n</div>\n```\n\nIn the current implementation, newlines and indentation between `<div>` and `<span>` are preserved as text nodes.\\\nThis generates unnecessary nodes and can affect performance.\n\n### Vue.js's Approach\n\nVue.js uses the `whitespace` option to control how whitespace is handled.\n\n```ts\ntype WhitespaceStrategy = 'preserve' | 'condense'\n```\n\n- **`'condense'`** (default): Condense consecutive whitespace and remove unnecessary whitespace\n- **`'preserve'`**: Preserve whitespace as-is\n\n### Condense Mode Behavior\n\nIn condense mode, whitespace is processed according to the following rules:\n\n1. **Whitespace-only text nodes at the start/end** → Remove\n2. **Whitespace between elements containing newlines** → Remove\n3. **Consecutive whitespace** → Condense to a single space\n4. **Whitespace between elements without newlines** → Preserve (condensed to single space)\n\nExamples:\n\n```html\n<div>   <span/>    </div>\n<!-- Result: Only <span/> as child node (surrounding spaces removed) -->\n\n<div/>\n<div/>\n<div/>\n<!-- Result: Only 3 div elements (whitespace with newlines removed) -->\n\n<span>foo</span>  <span>bar</span>\n<!-- Result: Space between elements is preserved (no newlines) -->\n```\n\n### Implementation\n\nFirst, add the `whitespace` option to `ParserOptions`.\n\n`packages/compiler-core/src/options.ts`:\n\n```ts\nexport interface ParserOptions {\n  // ... existing options ...\n  whitespace?: 'preserve' | 'condense' // [!code ++]\n}\n```\n\nAdd whitespace processing functions to `packages/compiler-core/src/parse.ts`.\n\n```ts\nfunction isAllWhitespace(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (\n      c !== 0x20 && // space\n      c !== 0x09 && // tab\n      c !== 0x0a && // newline\n      c !== 0x0c && // form feed\n      c !== 0x0d    // carriage return\n    ) {\n      return false\n    }\n  }\n  return true\n}\n\nfunction hasNewlineChar(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (c === 0x0a || c === 0x0d) {\n      return true\n    }\n  }\n  return false\n}\n\nfunction condense(content: string): string {\n  let result = ''\n  let prevIsWhitespace = false\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    const isWhitespace =\n      c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d\n    if (isWhitespace) {\n      if (!prevIsWhitespace) {\n        result += ' '\n        prevIsWhitespace = true\n      }\n    } else {\n      result += content[i]\n      prevIsWhitespace = false\n    }\n  }\n  return result\n}\n\nfunction condenseWhitespace(\n  nodes: TemplateChildNode[],\n  context: ParserContext,\n): TemplateChildNode[] {\n  const shouldCondense = context.options.whitespace !== 'preserve'\n  let removedWhitespace = false\n\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i]\n    if (node.type === NodeTypes.TEXT) {\n      if (!context.inPre) {\n        if (isAllWhitespace(node.content)) {\n          const prev = nodes[i - 1]?.type\n          const next = nodes[i + 1]?.type\n          // Remove if:\n          // - First or last whitespace\n          // - (condense mode) Whitespace between comments\n          // - (condense mode) Whitespace between comment and element\n          // - (condense mode) Whitespace between elements containing newlines\n          if (\n            !prev ||\n            !next ||\n            (shouldCondense &&\n              ((prev === NodeTypes.COMMENT &&\n                (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||\n                (prev === NodeTypes.ELEMENT &&\n                  (next === NodeTypes.COMMENT ||\n                    (next === NodeTypes.ELEMENT &&\n                      hasNewlineChar(node.content))))))\n          ) {\n            removedWhitespace = true\n            nodes[i] = null as any\n          } else {\n            // Otherwise, condense to single space\n            node.content = ' '\n          }\n        } else if (shouldCondense) {\n          // In condense mode, condense consecutive whitespace\n          node.content = condense(node.content)\n        }\n      }\n    }\n  }\n\n  return removedWhitespace ? nodes.filter(Boolean) : nodes\n}\n```\n\nThen call this function when parsing elements.\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // ... existing code ...\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = condenseWhitespace(children, context) // [!code ++]\n    // element.children = children // [!code --]\n\n    // ...\n  }\n\n  return element\n}\n```\n\nAlso apply the same processing to the root node.\n\n```ts\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {},\n): RootNode => {\n  const context = createParserContext(content, options)\n  const children = parseChildren(context, [])\n  return createRoot(condenseWhitespace(children, context)) // [!code ++]\n  // return createRoot(children) // [!code --]\n}\n```\n\n## Text Node Merging (transformText)\n\n### The Problem\n\nIn the current implementation, text nodes and mustache syntax (`{{ }}`) are treated as separate nodes.\n\n```html\n<div>abc {{ d }} {{ e }}</div>\n```\n\nThis template has the following child nodes:\n- `TEXT`: \"abc \"\n- `INTERPOLATION`: d\n- `TEXT`: \" \"\n- `INTERPOLATION`: e\n\nProcessing these individually during code generation is inefficient.\n\n### Vue.js's Approach\n\nVue.js uses a transformer called `transformText` to merge adjacent text nodes and mustache syntax into a single `CompoundExpression`.\n\nAfter merging:\n```ts\n// \"abc \" + d + \" \" + e\ncreateCompoundExpression(['abc ', d, ' ', e])\n```\n\nThis allows efficient concatenation operations during code generation.\n\n### Implementation\n\nCreate `packages/compiler-core/src/transforms/transformText.ts`.\n\n```ts\nimport type { NodeTransform } from '../transform'\nimport {\n  type CompoundExpressionNode,\n  ElementTypes,\n  NodeTypes,\n  createCallExpression,\n  createCompoundExpression,\n} from '../ast'\nimport { isText } from '../utils'\nimport { CREATE_TEXT } from '../runtimeHelpers'\nimport { PatchFlags } from '@chibivue/shared'\n\n// Merge adjacent text nodes and mustaches into a single expression\n// e.g. <div>abc {{ d }} {{ e }}</div> should have a single child node\nexport const transformText: NodeTransform = (node, context) => {\n  if (\n    node.type === NodeTypes.ROOT ||\n    node.type === NodeTypes.ELEMENT ||\n    node.type === NodeTypes.FOR ||\n    node.type === NodeTypes.IF_BRANCH\n  ) {\n    // Execute after child processing is complete\n    return () => {\n      const children = node.children\n      let currentContainer: CompoundExpressionNode | undefined = undefined\n      let hasText = false\n\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child)) {\n          hasText = true\n          for (let j = i + 1; j < children.length; j++) {\n            const next = children[j]\n            if (isText(next)) {\n              if (!currentContainer) {\n                currentContainer = children[i] = createCompoundExpression(\n                  [child],\n                  child.loc,\n                )\n              }\n              // Merge adjacent text nodes\n              currentContainer.children.push(` + `, next)\n              children.splice(j, 1)\n              j--\n            } else {\n              currentContainer = undefined\n              break\n            }\n          }\n        }\n      }\n\n      if (\n        !hasText ||\n        // Leave plain elements with a single text child as-is\n        // Runtime has optimized fast path for directly setting textContent\n        (children.length === 1 &&\n          (node.type === NodeTypes.ROOT ||\n            (node.type === NodeTypes.ELEMENT &&\n              node.tagType === ElementTypes.ELEMENT &&\n              !node.props.find(\n                p =>\n                  p.type === NodeTypes.DIRECTIVE &&\n                  !context.directiveTransforms[p.name],\n              ))))\n      ) {\n        return\n      }\n\n      // Convert text nodes to createTextVNode(text) calls\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {\n          const callArgs: any[] = []\n          // createTextVNode defaults to single space,\n          // so we can omit the argument for single space\n          if (child.type !== NodeTypes.TEXT || child.content !== ' ') {\n            callArgs.push(child)\n          }\n          // Mark dynamic text with flag for patching inside a block\n          if (!context.ssr && !isStaticNode(child)) {\n            callArgs.push(PatchFlags.TEXT)\n          }\n          children[i] = {\n            type: NodeTypes.TEXT_CALL,\n            content: child,\n            loc: child.loc,\n            codegenNode: createCallExpression(\n              context.helper(CREATE_TEXT),\n              callArgs,\n            ),\n          }\n        }\n      }\n    }\n  }\n}\n\nfunction isStaticNode(node: any): boolean {\n  if (node.type === NodeTypes.TEXT) {\n    return true\n  }\n  if (node.type === NodeTypes.INTERPOLATION) {\n    return node.content.isStatic\n  }\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    return node.children.every((child: any) => {\n      if (typeof child === 'string') return true\n      return isStaticNode(child)\n    })\n  }\n  return false\n}\n```\n\nAdd the `isText` helper to `packages/compiler-core/src/utils.ts`.\n\n```ts\nexport function isText(\n  node: TemplateChildNode,\n): node is TextNode | InterpolationNode {\n  return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION\n}\n```\n\nAdd `TEXT_CALL` node type and `createCallExpression` to `packages/compiler-core/src/ast.ts`.\n\n```ts\nexport const enum NodeTypes {\n  // ... existing types ...\n  TEXT_CALL, // [!code ++]\n}\n\nexport interface TextCallNode extends Node {\n  type: NodeTypes.TEXT_CALL\n  content: TextNode | InterpolationNode | CompoundExpressionNode\n  codegenNode: CallExpression\n}\n\nexport function createCallExpression(\n  callee: string,\n  args: CallExpression['arguments'] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  }\n}\n```\n\nAdd `CREATE_TEXT` to `packages/compiler-core/src/runtimeHelpers.ts`.\n\n```ts\nexport const CREATE_TEXT = Symbol('createTextVNode')\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ... existing helpers ...\n  [CREATE_TEXT]: 'createTextVNode',\n}\n```\n\n### Registering the Transformer\n\nRegister the transformer in `packages/compiler-core/src/compile.ts`.\n\n```ts\nimport { transformText } from './transforms/transformText'\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformElement,\n      transformSlotOutlet,\n      transformText, // [!code ++]\n    ],\n    {\n      on: transformOn,\n      bind: transformBind,\n      if: transformIf,\n      for: transformFor,\n      model: transformModel,\n    },\n  ]\n}\n```\n\n### Updating Code Generation\n\nAdd `TEXT_CALL` node handling to `packages/compiler-core/src/codegen.ts`.\n\n```ts\nfunction genNode(node: any, context: CodegenContext) {\n  switch (node.type) {\n    // ... existing cases ...\n    case NodeTypes.TEXT_CALL: // [!code ++]\n      genNode(node.codegenNode, context) // [!code ++]\n      break // [!code ++]\n  }\n}\n```\n\n### Updating the Runtime\n\nAdd `createTextVNode` to `packages/runtime-core/src/vnode.ts`.\n\n```ts\nexport function createTextVNode(text: string = ' ', flag: number = 0): VNode {\n  return createVNode(Text, null, text, flag)\n}\n```\n\nExport this from `packages/runtime-core/src/index.ts`.\n\n```ts\nexport { createTextVNode } from './vnode'\n```\n\n## Testing\n\nLet's verify with the following template:\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const name = ref('World')\n    return { name }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello {{ name }}!</p>\n  </div>\n</template>\n```\n\nWhen you check the compilation result, you should see:\n- Unnecessary whitespace (newlines and indentation) has been removed\n- `Hello `, `{{ name }}`, and `!` have been merged\n\nThe compiler quality has now been improved!\n\nSource code up to this point:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/100_chore_compiler)\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/110-parser-optimization.md",
    "content": "# Parser Optimization\n\n::: info About this chapter\nThis chapter explains the new parser architecture introduced in Vue 3.4.\\\nWith a state-machine tokenizer based on htmlparser2, parse speed has improved by 2x.\n:::\n\n## Background\n\nIn Vue 3.4, the internal implementation of the template compiler was significantly refactored. The parser we have implemented in chibivue so far is based on the architecture from Vue 3.3 and earlier.\n\n### Traditional Parser (Vue 3.3 and earlier)\n\nThe traditional Vue parser was a **recursive descent parser**:\n\n```ts\n// Traditional implementation\nfunction parseChildren(context: ParserContext): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined\n\n    if (startsWith(s, '{{')) {\n      node = parseInterpolation(context)\n    } else if (s[0] === '<') {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context)\n      }\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\nProblems with this approach:\n- Heavy use of **regular expressions**\n- Frequent **look-ahead searches**\n- Multiple passes through the template string\n\n### New Parser (Vue 3.4)\n\nVue 3.4 introduced a **state-machine tokenizer** based on [htmlparser2](https://github.com/fb55/htmlparser2):\n\n```ts\n// New implementation\nconst enum State {\n  Text,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  BeforeAttrName,\n  InAttrName,\n  // ...\n}\n\nclass Tokenizer {\n  private state = State.Text\n  private index = 0\n\n  parse(input: string) {\n    for (let i = 0; i < input.length; i++) {\n      this.index = i\n      this.consume(input.charCodeAt(i))\n    }\n  }\n\n  private consume(char: number) {\n    switch (this.state) {\n      case State.Text:\n        this.handleText(char)\n        break\n      case State.BeforeTagName:\n        this.handleBeforeTagName(char)\n        break\n      // ...\n    }\n  }\n}\n```\n\nBenefits of this approach:\n- **Single pass** through the template string\n- No regular expressions (or minimal use)\n- **Character-by-character** processing is efficient\n- Clear **state transitions** improve maintainability\n\n<KawaikoNote variant=\"surprise\" title=\"2x Faster!\">\n\nThis state-machine tokenizer achieves a **consistent 2x speedup** in parse time!\\\nIt's amazing that such significant performance improvements can be achieved simply by avoiding regular expressions and look-ahead searches, processing one character at a time.\n\n</KawaikoNote>\n\n## State Machine Tokenizer\n\nThe state machine tokenizer determines how to process the next character based on the current state.\n\n### State Definitions\n\n```ts\nconst enum State {\n  // Text\n  Text = 1,\n\n  // Interpolation (Mustache)\n  InterpolationOpen,     // Detecting {{\n  Interpolation,         // Content inside {{\n  InterpolationClose,    // Detecting }}\n\n  // Tags\n  BeforeTagName,         // After <\n  InTagName,             // Inside tag name\n  InSelfClosingTag,      // Detecting />\n\n  // Attributes\n  BeforeAttrName,        // Before attribute name\n  InAttrName,            // Inside attribute name\n  AfterAttrName,         // After attribute name (before =)\n  BeforeAttrValue,       // Before attribute value\n  InAttrValueDq,         // Attribute value in double quotes\n  InAttrValueSq,         // Attribute value in single quotes\n  InAttrValueNq,         // Unquoted attribute value\n\n  // Directives\n  InDirName,             // Directive name (v-xxx)\n  InDirArg,              // Directive argument (:xxx)\n  InDirDynamicArg,       // Dynamic argument ([xxx])\n  InDirModifier,         // Modifier (.xxx)\n}\n```\n\n### State Transition Example\n\n```\n<div v-if=\"show\">Hello {{ name }}</div>\n```\n\nState transitions for this example:\n\n```\n< → BeforeTagName\nd → InTagName\ni → InTagName\nv → InTagName\n(space) → BeforeAttrName\nv → InAttrName (or InDirName)\n- → InDirName\ni → InDirName\nf → InDirName\n= → BeforeAttrValue\n\" → InAttrValueDq\ns → InAttrValueDq\nh → InAttrValueDq\no → InAttrValueDq\nw → InAttrValueDq\n\" → BeforeAttrName\n> → Text\nH → Text\n...\n{ → InterpolationOpen\n{ → Interpolation\n(space) → Interpolation\nn → Interpolation\na → Interpolation\nm → Interpolation\ne → Interpolation\n(space) → Interpolation\n} → InterpolationClose\n} → Text\n...\n```\n\n## Visitor Pattern\n\nThe new parser uses the **Visitor pattern** to separate the tokenizer from AST construction.\n\n### Callbacks Interface\n\n```ts\ninterface Callbacks {\n  onText(start: number, end: number): void\n  onInterpolation(start: number, end: number): void\n  onOpenTag(tag: string, start: number): void\n  onCloseTag(tag: string, start: number, end: number): void\n  onSelfClosingTag(tag: string, start: number, end: number): void\n  onAttr(name: string, value: string | undefined, start: number, end: number): void\n  onDirective(\n    name: string,\n    arg: string | undefined,\n    modifiers: string[],\n    value: string | undefined,\n    start: number,\n    end: number\n  ): void\n  onComment(start: number, end: number): void\n}\n```\n\n### Separation of Tokenizer and Parser\n\n```ts\nclass Tokenizer {\n  private cbs: Callbacks\n\n  constructor(callbacks: Callbacks) {\n    this.cbs = callbacks\n  }\n\n  // Tokenizer emits events\n  private emitOpenTag(tag: string, start: number) {\n    this.cbs.onOpenTag(tag, start)\n  }\n\n  private emitText(start: number, end: number) {\n    this.cbs.onText(start, end)\n  }\n}\n\n// Parser implements Callbacks to build AST\nclass Parser implements Callbacks {\n  private stack: ElementNode[] = []\n  private root: RootNode\n\n  onOpenTag(tag: string, start: number) {\n    const element: ElementNode = {\n      type: NodeTypes.ELEMENT,\n      tag,\n      children: [],\n      // ...\n    }\n    this.stack.push(element)\n  }\n\n  onCloseTag(tag: string, start: number, end: number) {\n    const element = this.stack.pop()!\n    const parent = this.stack[this.stack.length - 1]\n    if (parent) {\n      parent.children.push(element)\n    } else {\n      this.root.children.push(element)\n    }\n  }\n\n  onText(start: number, end: number) {\n    const parent = this.stack[this.stack.length - 1]\n    const text: TextNode = {\n      type: NodeTypes.TEXT,\n      content: this.source.slice(start, end),\n      // ...\n    }\n    parent.children.push(text)\n  }\n}\n```\n\n### Benefits\n\n1. **Separation of concerns**: Tokenizer focuses only on character parsing, Parser focuses only on AST construction\n2. **Testability**: Each component can be tested independently\n3. **Reusability**: Tokenizer can be reused for other purposes (syntax highlighting, linting, etc.)\n4. **Performance**: No unnecessary intermediate data structures\n\n<KawaikoNote variant=\"question\" title=\"What is the Visitor Pattern?\">\n\nThe Visitor pattern is a design pattern that \"separates data structure from its processing\".\\\nThe Tokenizer \"just reads the template and emits events\", while the Parser \"just receives events and builds the AST\" - a simple division of responsibilities.\\\nThis makes the code easier to understand and test!\n\n</KawaikoNote>\n\n## Performance Comparison\n\nAccording to the Vue 3.4 blog post:\n\n| Template Size | Improvement |\n|--------------|-------------|\n| Small | ~2x |\n| Medium | ~2x |\n| Large | ~2x |\n\nA consistent 2x speedup has been achieved.\n\nThis improvement benefits the entire ecosystem:\n- **Volar**: IDE completion and type checking\n- **vue-tsc**: Type checking\n- **Build tools**: Vite, Webpack, etc.\n- **Community plugins**: ESLint, Prettier, etc.\n\n## Implementation in chibivue\n\n::: warning\nCurrent chibivue uses the traditional recursive descent parser.\\\nMigration to a Vue 3.4-style tokenizer is being considered for future work.\n:::\n\nBasic implementation outline:\n\n<KawaikoNote variant=\"base\" title=\"Challenge Yourself!\">\n\nThe state-machine tokenizer introduced in this chapter is not yet implemented in chibivue, but if you're interested, try implementing it yourself!\\\nReferring to Vue 3.4's source code and htmlparser2 will deepen your understanding.\\\nParser optimization is a very important skill in framework development.\n\n</KawaikoNote>\n\n```ts\n// packages/compiler-core/tokenizer.ts\nconst enum State {\n  Text = 1,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  // ...\n}\n\nconst enum CharCodes {\n  Lt = 0x3c,      // <\n  Gt = 0x3e,      // >\n  Slash = 0x2f,   // /\n  Eq = 0x3d,      // =\n  OpenBrace = 0x7b,  // {\n  CloseBrace = 0x7d, // }\n  // ...\n}\n\nexport class Tokenizer {\n  private state = State.Text\n  private buffer = ''\n  private sectionStart = 0\n  private index = 0\n\n  constructor(private cbs: Callbacks) {}\n\n  parse(input: string) {\n    this.buffer = input\n    while (this.index < input.length) {\n      const c = input.charCodeAt(this.index)\n      switch (this.state) {\n        case State.Text:\n          this.stateText(c)\n          break\n        case State.InterpolationOpen:\n          this.stateInterpolationOpen(c)\n          break\n        // ...\n      }\n      this.index++\n    }\n    this.finish()\n  }\n\n  private stateText(c: number) {\n    if (c === CharCodes.Lt) {\n      if (this.index > this.sectionStart) {\n        this.cbs.onText(this.sectionStart, this.index)\n      }\n      this.state = State.BeforeTagName\n      this.sectionStart = this.index\n    } else if (c === CharCodes.OpenBrace) {\n      this.state = State.InterpolationOpen\n    }\n  }\n\n  private stateInterpolationOpen(c: number) {\n    if (c === CharCodes.OpenBrace) {\n      if (this.index > this.sectionStart + 1) {\n        this.cbs.onText(this.sectionStart, this.index - 1)\n      }\n      this.state = State.Interpolation\n      this.sectionStart = this.index + 1\n    } else {\n      this.state = State.Text\n    }\n  }\n\n  // ...\n}\n```\n\n## Summary\n\n- Vue 3.4 introduced a state-machine tokenizer based on htmlparser2\n- Parse speed improved by 2x by scanning the template string only once\n- Visitor pattern separates tokenizer and AST construction for better maintainability\n- This optimization benefits the entire ecosystem (Volar, vue-tsc, etc.)\n\n## References\n\n- [Announcing Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) - Vue Official Blog\n- [htmlparser2](https://github.com/fb55/htmlparser2) - The library the tokenizer is based on\n- [Vue 3.4 Parser Refactor](https://github.com/vuejs/core/pull/9674) - GitHub PR\n"
  },
  {
    "path": "book/online-book/src/50-basic-template-compiler/500-custom-directive.md",
    "content": "# Custom Directives\n\n::: info About this chapter\nThis chapter implements Vue's custom directive feature.\\\nYou will learn how to define custom directives like `v-focus` and perform direct operations on elements.\n:::\n\n## What are Custom Directives?\n\nVue's custom directives are a feature for performing low-level operations on DOM elements. They are used when direct DOM manipulation is needed that cannot be handled through component abstraction.\n\nTypical use cases:\n\n- Auto-focus on elements (`v-focus`)\n- Click outside detection (`v-click-outside`)\n- Lazy loading of elements (`v-lazy`)\n- Tooltip display (`v-tooltip`)\n\n```vue\n<script setup>\n// Define a custom directive\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n</script>\n\n<template>\n  <input v-focus />\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"Honestly, rarely used\">\n\nCustom directives are used when you \"want to directly touch the DOM\", but honestly they're not used very often.\\\nDue to implementation changes in Vapor Mode and poor compatibility with static analysis, **if you don't need to use them, don't use them**.\\\nHandle things with components whenever possible!\n\n</KawaikoNote>\n\n## Directive Lifecycle\n\nDirectives have lifecycle hooks similar to components:\n\n```ts\nconst myDirective = {\n  // Before element's attributes or event listeners are applied\n  created(el, binding, vnode, prevVnode) {},\n\n  // Right before element is inserted into DOM\n  beforeMount(el, binding, vnode, prevVnode) {},\n\n  // After element is inserted into DOM\n  mounted(el, binding, vnode, prevVnode) {},\n\n  // Before parent component is updated\n  beforeUpdate(el, binding, vnode, prevVnode) {},\n\n  // After parent and children have updated\n  updated(el, binding, vnode, prevVnode) {},\n\n  // Before parent component is unmounted\n  beforeUnmount(el, binding, vnode, prevVnode) {},\n\n  // After parent component is unmounted\n  unmounted(el, binding, vnode, prevVnode) {},\n}\n```\n\nEach hook receives the following arguments:\n\n- `el`: The element the directive is bound to\n- `binding`: Information passed to the directive (value, argument, etc.)\n- `vnode`: The VNode corresponding to el\n- `prevVnode`: The previous VNode (only for beforeUpdate, updated)\n\n## Implementation Overview\n\nThe custom directive implementation consists of three parts:\n\n1. **Runtime side**: Directive type definitions and `withDirectives` helper\n2. **Renderer side**: Hook invocation at each lifecycle\n3. **Compiler side**: Generate `withDirectives` from templates\n\n## Runtime Implementation\n\n### Directive Type Definitions\n\nFirst, define the directive types:\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentPublicInstance | null\n  value: V\n  oldValue: V | null\n  arg?: string\n  dir: ObjectDirective<any>\n}\n\nexport type DirectiveHook<T = any> = (\n  el: T,\n  binding: DirectiveBinding,\n  vnode: VNode,\n  prevVNode: VNode | null\n) => void\n\nexport interface ObjectDirective<T = any> {\n  created?: DirectiveHook<T>\n  beforeMount?: DirectiveHook<T>\n  mounted?: DirectiveHook<T>\n  beforeUpdate?: DirectiveHook<T>\n  updated?: DirectiveHook<T>\n  beforeUnmount?: DirectiveHook<T>\n  unmounted?: DirectiveHook<T>\n}\n```\n\n### withDirectives Helper\n\nThe compiler generates code that wraps directive-bound elements with `withDirectives`:\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport type DirectiveArguments = Array<\n  | [ObjectDirective | undefined]\n  | [ObjectDirective | undefined, any]\n  | [ObjectDirective | undefined, any, string]\n>\n\nexport function withDirectives<T extends VNode>(\n  vnode: T,\n  directives: DirectiveArguments\n): T {\n  const internalInstance = currentRenderingInstance\n  if (internalInstance === null) return vnode\n\n  const instance = internalInstance.proxy\n\n  const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])\n  for (let i = 0; i < directives.length; i++) {\n    let [dir, value, arg] = directives[i]\n    if (dir) {\n      // Convert function-style directive to object style\n      if (isFunction(dir)) {\n        dir = {\n          mounted: dir,\n          updated: dir,\n        } as ObjectDirective\n      }\n      bindings.push({\n        dir,\n        instance,\n        value,\n        oldValue: void 0,\n        arg,\n      })\n    }\n  }\n  return vnode\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"Simple!\">\n\n`withDirectives` just adds the `dirs` property to the VNode.\\\nThe actual hook invocation is done by the renderer, so this implementation simply attaches information to the VNode!\n\n</KawaikoNote>\n\n### Invoking Directive Hooks\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport function invokeDirectiveHook(\n  vnode: VNode,\n  prevVNode: VNode | null,\n  name: keyof ObjectDirective\n): void {\n  const bindings = vnode.dirs!\n  const oldBindings = prevVNode && prevVNode.dirs!\n\n  for (let i = 0; i < bindings.length; i++) {\n    const binding = bindings[i]\n    // Set old value on update\n    if (oldBindings) {\n      binding.oldValue = oldBindings[i].value\n    }\n\n    const hook = binding.dir[name] as DirectiveHook | undefined\n    if (hook) {\n      hook(vnode.el, binding, vnode, prevVNode)\n    }\n  }\n}\n```\n\n## Renderer Implementation\n\nThe renderer calls `invokeDirectiveHook` at each timing during element mounting and updating:\n\n```ts\n// packages/runtime-core/src/renderer.ts\n\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const { type, props, children, dirs } = vnode\n\n  const el = (vnode.el = hostCreateElement(type as string))\n\n  // Mount children\n  if (typeof children === 'string') {\n    hostSetElementText(el, children)\n  } else if (isArray(children)) {\n    mountChildren(children as VNodeArrayChildren, el, null, parentComponent)\n  }\n\n  // Directive: created hook\n  dirs && invokeDirectiveHook(vnode, null, 'created')\n\n  // Set props\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, null, props[key])\n    }\n  }\n\n  // Directive: beforeMount hook\n  dirs && invokeDirectiveHook(vnode, null, 'beforeMount')\n\n  // Insert into DOM\n  hostInsert(el, container, anchor!)\n\n  // Directive: mounted hook\n  dirs && invokeDirectiveHook(vnode, null, 'mounted')\n}\n\nconst patchElement = (\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const el = (n2.el = n1.el!)\n  const { dirs } = n2\n  const oldProps = n1.props ?? {}\n  const newProps = n2.props ?? {}\n\n  // Directive: beforeUpdate hook\n  dirs && invokeDirectiveHook(n2, n1, 'beforeUpdate')\n\n  // Update children and props\n  patchChildren(n1, n2, el, null, parentComponent)\n  patchProps(el, oldProps, newProps)\n\n  // Directive: updated hook\n  dirs && invokeDirectiveHook(n2, n1, 'updated')\n}\n```\n\n## Adding dirs Property to VNode\n\nAdd `dirs` to the VNode type definition:\n\n```ts\n// packages/runtime-core/src/vnode.ts\n\nexport interface VNode<ExtraProps = { [key: string]: any }> {\n  type: VNodeTypes\n  props: (VNodeProps & ExtraProps) | null\n  children: VNodeNormalizedChildren\n  el: RendererNode | null\n  key: string | number | symbol | null\n  ref: Ref | null\n  shapeFlag: number\n  dirs?: DirectiveBinding[] | null  // Added\n}\n```\n\n## Compiler Implementation\n\n### Registering WITH_DIRECTIVES Helper\n\n```ts\n// packages/compiler-core/src/runtimeHelpers.ts\n\nexport const WITH_DIRECTIVES: unique symbol = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_DIRECTIVES]: 'withDirectives',\n}\n```\n\n### Code Generation\n\nWhen a VNode has directives, wrap it with `withDirectives`:\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper } = context\n  const { tag, props, children, directives } = node\n\n  // Wrap with withDirectives if directives exist\n  if (directives) {\n    push(helper(WITH_DIRECTIVES) + `(`)\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`, node)\n  genNodeList(genNullableArgs([tag, props, children]), context)\n  push(`)`)\n\n  if (directives) {\n    push(`, `)\n    genNode(directives, context)\n    push(`)`)\n  }\n}\n```\n\nExample of generated code:\n\n```ts\n// Template: <input v-focus />\n\n// Generated code\nwithDirectives(\n  createElementVNode('input'),\n  [[vFocus]]\n)\n\n// Template: <div v-my-directive:arg.modifier=\"value\" />\n\n// Generated code\nwithDirectives(\n  createElementVNode('div'),\n  [[vMyDirective, value, 'arg', { modifier: true }]]\n)\n```\n\n## Testing\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\n\n// v-focus directive\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n\n// v-color directive\nconst vColor = {\n  mounted(el, binding) {\n    el.style.color = binding.value\n  },\n  updated(el, binding) {\n    el.style.color = binding.value\n  }\n}\n\nconst color = ref('red')\n</script>\n\n<template>\n  <input v-focus placeholder=\"Auto focus\" />\n\n  <p v-color=\"color\">This text is {{ color }}</p>\n\n  <button @click=\"color = 'blue'\">Make Blue</button>\n  <button @click=\"color = 'green'\">Make Green</button>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"Implementation Complete!\">\n\nThe custom directive implementation is complete!\\\nWith the runtime, renderer, and compiler working together, you can now use custom directives like `v-focus`.\\\nv-model is also implemented internally as a directive, so check it out!\n\n</KawaikoNote>\n\n## Summary\n\n- Custom directives are a low-level API for direct DOM manipulation\n- `withDirectives` attaches directive information to VNodes\n- The renderer calls hooks at each lifecycle\n- The compiler generates `withDirectives` from templates\n\n## References\n\n- [Vue.js - Custom Directives](https://vuejs.org/guide/reusability/custom-directives.html) - Vue Official Documentation\n"
  },
  {
    "path": "book/online-book/src/60-basic-sfc-compiler/010-script-setup.md",
    "content": "# Supporting script setup\n\n::: info About this chapter\nThis chapter explains how to implement Vue 3's `<script setup>` syntax.\\\nLearn how script setup works to write components more concisely.\n:::\n\n## What is script setup?\n\n`<script setup>` is a compile-time syntactic sugar introduced in Vue 3.2. It allows you to write components more concisely compared to the traditional Options API or Composition API.\n\n```vue\n<!-- Traditional way -->\n<script>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: { MyComponent },\n  setup() {\n    const count = ref(0)\n    const increment = () => count.value++\n    return { count, increment }\n  }\n}\n</script>\n\n<!-- script setup way -->\n<script setup>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n```\n\n<KawaikoNote variant=\"surprise\" title=\"So much shorter!\">\n\nWith script setup, you don't need `export default` or `return`, and imported components are automatically registered.\\\nThe code becomes much cleaner!\n\n</KawaikoNote>\n\n## Implementation Overview\n\nCompiling script setup involves the following steps:\n\n1. **Import analysis and hoisting**: Extract import statements and move them to the top of the file\n2. **Binding analysis**: Track variable declarations and function definitions\n3. **Macro processing**: Handle defineProps, defineEmits, etc. (covered in later chapters)\n4. **Code transformation**: Transform into setup function and generate return statement\n\n## The compileScript Function\n\nThe `compileScript` function is the central function that compiles the script portion of an SFC.\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nexport function compileScript(\n  sfc: SFCDescriptor,\n  options: SFCScriptCompileOptions,\n): SFCScriptBlock {\n  let { script, scriptSetup, source } = sfc\n\n  // Parse with Babel\n  const scriptAst = _parse(script?.content ?? \"\", { sourceType: \"module\" }).program\n  const scriptSetupAst = _parse(scriptSetup?.content ?? \"\", { sourceType: \"module\" }).program\n\n  // Traditional processing if no script setup\n  if (!scriptSetup) {\n    if (!script) {\n      throw new Error(`SFC contains no <script> tags.`)\n    }\n    return { ...script, bindings: analyzeScriptBindings(scriptAst.body) }\n  }\n\n  // Initialize metadata\n  const bindingMetadata: BindingMetadata = {}\n  const userImports: Record<string, ImportBinding> = Object.create(null)\n  const setupBindings: Record<string, BindingTypes> = Object.create(null)\n\n  const s = new MagicString(source)\n  // ... transformation processing\n}\n```\n\n## Import Hoisting\n\nImport statements inside script setup need to be moved (hoisted) to the beginning of the generated code.\n\n```ts\n// 1.2 walk import declarations of <script setup>\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ImportDeclaration\") {\n    // Move import to file top\n    hoistNode(node)\n\n    // Remove duplicate imports\n    for (let i = 0; i < node.specifiers.length; i++) {\n      const specifier = node.specifiers[i]\n      const local = specifier.local.name\n      const imported = getImportedName(specifier)\n      const source = node.source.value\n\n      const existing = userImports[local]\n      if (existing) {\n        if (existing.source === source && existing.imported === imported) {\n          removeSpecifier(i)\n        }\n      } else {\n        registerUserImport(source, local, imported, true)\n      }\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"question\" title=\"Why is hoisting necessary?\">\n\nIn the generated code, import statements need to be placed outside the `setup()` function.\\\nHoisting moves imports written inside `<script setup>` to the correct position.\n\nBy the way, `export` inside `<script setup>` will cause an error.\\\nHowever, `export type` is OK since it's only type information!\n\n</KawaikoNote>\n\n## Binding Analysis\n\nTo correctly resolve variables referenced from the template, we analyze bindings in the script.\n\n```ts\nfunction walkDeclaration(\n  node: Declaration,\n  bindings: Record<string, BindingTypes>,\n  userImportAliases: Record<string, string> = {},\n) {\n  if (node.type === \"VariableDeclaration\") {\n    const isConst = node.kind === \"const\"\n\n    for (const { id, init } of node.declarations) {\n      if (id.type === \"Identifier\") {\n        let bindingType\n        if (isConst && isStaticNode(init!)) {\n          bindingType = BindingTypes.LITERAL_CONST\n        } else if (isCallOf(init, userImportAliases[\"reactive\"])) {\n          bindingType = BindingTypes.SETUP_REACTIVE_CONST\n        } else if (isCallOf(init, userImportAliases[\"ref\"])) {\n          bindingType = BindingTypes.SETUP_REF\n        } else if (isConst) {\n          bindingType = BindingTypes.SETUP_MAYBE_REF\n        } else {\n          bindingType = BindingTypes.SETUP_LET\n        }\n        registerBinding(bindings, id, bindingType)\n      }\n    }\n  } else if (node.type === \"FunctionDeclaration\") {\n    bindings[node.id!.name] = BindingTypes.SETUP_CONST\n  }\n}\n```\n\nThe binding type determines how the variable is referenced in the template:\n\n| Type | Description | Template Reference |\n|------|-------------|-------------------|\n| `SETUP_REF` | Created with ref() | Auto-adds `.value` |\n| `SETUP_REACTIVE_CONST` | Created with reactive() | Direct reference |\n| `SETUP_CONST` | Constant | Direct reference |\n| `SETUP_LET` | let/var variable | Direct reference |\n\n## Inline Template\n\nWhen using script setup, the template can be inlined inside the setup function.\n\n```ts\n// 10. generate return statement\nlet returned\nif (options.inlineTemplate) {\n  if (sfc.template) {\n    const { code, preamble } = compileTemplate({\n      source: sfc.template.content.trim(),\n      compilerOptions: { inline: true, bindingMetadata },\n    })\n\n    if (preamble) {\n      s.prepend(preamble)\n    }\n    returned = code\n  } else {\n    returned = `() => {}`\n  }\n}\ns.appendRight(endOffset, `\\nreturn ${returned}\\n`)\n```\n\nExample of generated code:\n\n```ts\n// Input\n// <script setup>\n// import { ref } from 'chibivue'\n// const count = ref(0)\n// </script>\n// <template>\n//   <p>{{ count }}</p>\n// </template>\n\n// Output\nimport { ref } from 'chibivue'\n\nexport default {\n  setup(__props) {\n    const count = ref(0)\n\n    return (_ctx) => {\n      return h('p', count.value)\n    }\n  }\n}\n```\n\n## Integration with Vite Plugin\n\nThe Vite plugin detects and compiles script setup.\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/script.ts\n\nexport function resolveScript(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n): SFCScriptBlock | null {\n  if (!descriptor.script && !descriptor.scriptSetup) return null\n\n  return options.compiler.compileScript(descriptor, {\n    inlineTemplate: isUseInlineTemplate(descriptor),\n  })\n}\n\nexport function isUseInlineTemplate(descriptor: SFCDescriptor): boolean {\n  return !!descriptor.scriptSetup\n}\n```\n\n## Testing\n\n```vue\n<script setup>\nimport { ref, computed } from 'chibivue'\n\nconst count = ref(0)\nconst double = computed(() => count.value * 2)\n\nconst increment = () => {\n  count.value++\n}\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ count }}</p>\n    <p>Double: {{ double }}</p>\n    <button @click=\"increment\">+1</button>\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"Implementation Complete!\">\n\nThe basic implementation of script setup is complete!\\\nYou can now write components much more concisely compared to the traditional way.\\\nIn the next chapter, we'll learn how to implement the `defineProps` and `defineEmits` macros.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/010_script_setup)\n\n## Summary\n\n- `<script setup>` is syntactic sugar for writing Composition API more concisely\n- `compileScript` handles the central transformation processing\n- Import hoisting and binding analysis are important steps\n- The template is inlined inside the setup function\n\n## References\n\n- [Vue.js - script setup](https://vuejs.org/api/sfc-script-setup.html) - Vue Official Documentation\n- [RFC: script setup](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/60-basic-sfc-compiler/020-define-props.md",
    "content": "# Supporting defineProps\n\n::: info About this chapter\nThis chapter explains how to implement the `defineProps` macro used in `<script setup>`.\\\nLearn how compiler macros work and how props declarations are processed.\n:::\n\n## What is defineProps?\n\n`defineProps` is a compiler macro for declaring component props inside `<script setup>`.\n\n```vue\n<script setup>\n// Runtime declaration\nconst props = defineProps({\n  title: String,\n  count: {\n    type: Number,\n    default: 0\n  }\n})\n\nconsole.log(props.title)\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"What's a compiler macro?\">\n\n`defineProps` is not a regular function. It's a **compiler macro**.\\\nIt gets special treatment at compile time and is erased at runtime.\\\nThat's why you can use it without importing!\n\n</KawaikoNote>\n\n## Implementation Overview\n\nProcessing defineProps involves the following steps:\n\n1. **Detect macro calls**: Find `defineProps()` calls in the AST\n2. **Extract arguments**: Get the props definition object\n3. **Remove code**: Delete the original `defineProps()` call\n4. **Add to options**: Add as `props` option to the output\n5. **Register bindings**: Register props as `PROPS` type\n\n## The processDefineProps Function\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_PROPS = \"defineProps\"\n\nlet propsRuntimeDecl: Node | undefined\nlet propsIdentifier: string | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  // Save the argument (props definition object)\n  propsRuntimeDecl = node.arguments[0]\n\n  // Save the identifier if assigned to a variable\n  // The \"props\" part in const props = defineProps(...)\n  if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST Traversal\n\nWe traverse the `<script setup>` body to detect `defineProps`.\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  // Expression statement (defineProps() called standalone)\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr)) {\n      // Remove the macro call\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  // Variable declaration (const props = defineProps(...))\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        if (processDefineProps(init, declId)) {\n          // Remove the declaration\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## Registering Props Bindings\n\nVariables declared as props are registered in binding metadata so they can be referenced from the template.\n\n```ts\n// 7. analyze binding metadata\nif (propsRuntimeDecl) {\n  for (const key of getObjectExpressionKeys(propsRuntimeDecl as ObjectExpression)) {\n    bindingMetadata[key] = BindingTypes.PROPS\n  }\n}\n```\n\nBy registering as `BindingTypes.PROPS`, the template compiler can correctly handle access to props.\n\n## Handling Props Identifier\n\nWhen assigned to a variable like `const props = defineProps(...)`, we make that variable accessible.\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\nif (propsIdentifier) {\n  // Add const props = __props;\n  s.prependLeft(startOffset, `\\nconst ${propsIdentifier} = __props;\\n`)\n}\n```\n\n## Adding to Options\n\nFinally, the props definition is output as a component option.\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  let declCode = scriptSetup.content\n    .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)\n    .trim()\n  runtimeOptions += `\\n  props: ${declCode},`\n}\n\ns.prependLeft(\n  startOffset,\n  `\\nexport default {\\n${runtimeOptions}\\nsetup(${args}) {\\n`\n)\n```\n\n## Transformation Example\n\n```vue\n<!-- Input -->\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n\n<template>\n  <h1>{{ title }}</h1>\n</template>\n```\n\n```ts\n// Output\nexport default {\n  props: {\n    title: String,\n    count: Number\n  },\n  setup(__props) {\n    const props = __props;\n\n    return (_ctx) => {\n      return h('h1', _ctx.title)\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"Simple!\">\n\n`defineProps` may look complex, but what it does is simple:\n1. Move arguments to `props` option\n2. Remove the `defineProps()` call\n3. If there's a variable, replace with reference to `__props`\n\n</KawaikoNote>\n\n## Testing\n\n```vue\n<script setup>\nimport { computed } from 'chibivue'\n\nconst props = defineProps({\n  firstName: String,\n  lastName: String\n})\n\nconst fullName = computed(() => `${props.firstName} ${props.lastName}`)\n</script>\n\n<template>\n  <div>\n    <p>First: {{ firstName }}</p>\n    <p>Last: {{ lastName }}</p>\n    <p>Full: {{ fullName }}</p>\n  </div>\n</template>\n```\n\nParent component:\n\n```vue\n<script setup>\nimport ChildComponent from './ChildComponent.vue'\n</script>\n\n<template>\n  <ChildComponent firstName=\"John\" lastName=\"Doe\" />\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"Implementation Complete!\">\n\nThe defineProps implementation is complete!\\\nYou now understand the basic mechanism of compiler macros.\\\nIn the next chapter, we'll learn how to implement the `defineEmits` macro.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/020_define_props)\n\n## Summary\n\n- `defineProps` is a compiler macro processed at compile time\n- Traverse the AST to detect `defineProps()` calls\n- Arguments are converted to `props` option, and the call itself is removed\n- Props are registered as `BindingTypes.PROPS` for template access\n\n## References\n\n- [Vue.js - defineProps](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue Official Documentation\n"
  },
  {
    "path": "book/online-book/src/60-basic-sfc-compiler/030-define-emits.md",
    "content": "# Supporting defineEmits\n\n::: info About this chapter\nThis chapter explains how to implement the `defineEmits` macro used in `<script setup>`.\\\nLearn how event emission from child to parent components works.\n:::\n\n## What is defineEmits?\n\n`defineEmits` is a compiler macro for declaring events that a component emits inside `<script setup>`.\n\n```vue\n<script setup>\nconst emit = defineEmits(['change', 'update'])\n\nfunction handleClick() {\n  emit('change', 'new value')\n}\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"How is it different from defineProps?\">\n\n`defineProps` is for passing data from parent to child,\\\n`defineEmits` is for notifying events from child to parent.\\\nRemember them as a pair!\n\n</KawaikoNote>\n\n## Implementation Overview\n\nProcessing defineEmits is very similar to defineProps:\n\n1. **Detect macro calls**: Find `defineEmits()` calls in the AST\n2. **Extract arguments**: Get the event definition array or object\n3. **Remove code**: Delete the original `defineEmits()` call\n4. **Add to options**: Add as `emits` option to the output\n5. **Provide emit function**: Get `emit` from setup's context\n\n## The processDefineEmits Function\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_EMITS = \"defineEmits\"\n\nlet emitsRuntimeDecl: Node | undefined\nlet emitIdentifier: string | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  // Save the event definition\n  emitsRuntimeDecl = node.arguments[0]\n\n  // Save the identifier if assigned to a variable\n  // The \"emit\" part in const emit = defineEmits(...)\n  if (declId) {\n    emitIdentifier =\n      declId.type === \"Identifier\"\n        ? declId.name\n        : scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST Traversal\n\nLike defineProps, we traverse the `<script setup>` body to detect `defineEmits`.\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr) || processDefineEmits(expr)) {\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        const isDefineProps = processDefineProps(init, declId)\n        const isDefineEmits = processDefineEmits(init, declId)\n        if (isDefineProps || isDefineEmits) {\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## Setting Up the emit Function\n\nThe emit function obtained from `defineEmits` is retrieved from the setup function's second argument (SetupContext).\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\n\nconst destructureElements: string[] = []\nif (emitIdentifier) {\n  destructureElements.push(\n    emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`\n  )\n}\n\nif (destructureElements.length) {\n  args += `, { ${destructureElements.join(\", \")} }`\n}\n```\n\nThis generates code like:\n\n```ts\n// For const emit = defineEmits(['change'])\nsetup(__props, { emit }) {\n  // ...\n}\n\n// For const emitFn = defineEmits(['change'])\nsetup(__props, { emit: emitFn }) {\n  // ...\n}\n```\n\n## Adding to Options\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  runtimeOptions += `\\n  props: ${...},`\n}\nif (emitsRuntimeDecl) {\n  runtimeOptions += `\\n  emits: ${scriptSetup.content\n    .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)\n    .trim()},`\n}\n```\n\n## Transformation Example\n\n```vue\n<!-- Input -->\n<script setup>\nconst emit = defineEmits(['update', 'delete'])\n\nfunction handleUpdate(value) {\n  emit('update', value)\n}\n</script>\n\n<template>\n  <button @click=\"handleUpdate('new')\">Update</button>\n</template>\n```\n\n```ts\n// Output\nexport default {\n  emits: ['update', 'delete'],\n  setup(__props, { emit }) {\n    function handleUpdate(value) {\n      emit('update', value)\n    }\n\n    return (_ctx) => {\n      return h('button', { onClick: _ctx.handleUpdate.bind(_ctx, 'new') }, 'Update')\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"Symmetrical with defineProps!\">\n\nThe `defineEmits` implementation follows almost the same pattern as `defineProps`:\n1. Detect macro call\n2. Move arguments to `emits` option\n3. If there's a variable, transform to get from SetupContext\n\nEasy to remember!\n\n</KawaikoNote>\n\n## Testing\n\nChild component:\n\n```vue\n<script setup>\nconst props = defineProps({\n  modelValue: String\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nfunction updateValue(e) {\n  emit('update:modelValue', e.target.value)\n}\n</script>\n\n<template>\n  <input :value=\"modelValue\" @input=\"updateValue\" />\n</template>\n```\n\nParent component:\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\nimport CustomInput from './CustomInput.vue'\n\nconst text = ref('')\n</script>\n\n<template>\n  <CustomInput v-model=\"text\" />\n  <p>Input value: {{ text }}</p>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"Implementation Complete!\">\n\nThe defineEmits implementation is complete!\\\nYou can now use both props and emits compiler macros.\\\nIn the next chapter, we'll learn how to implement scoped CSS.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/030_define_emits)\n\n## Summary\n\n- `defineEmits` is a macro for declaring events from child to parent\n- Processing pattern is very similar to `defineProps`\n- emit function is destructured from SetupContext\n- Added to component as `emits` option\n\n## References\n\n- [Vue.js - defineEmits](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue Official Documentation\n"
  },
  {
    "path": "book/online-book/src/60-basic-sfc-compiler/040-scoped-css.md",
    "content": "# Supporting Scoped CSS\n\n::: info About this chapter\nThis chapter explains how to implement Vue's Scoped CSS feature.\\\nLearn how to isolate styles per component and prevent style conflicts.\n:::\n\n## What is Scoped CSS?\n\nScoped CSS is a feature that applies styles defined in `<style scoped>` only to that component.\n\n```vue\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\nThis style will not affect elements with the same class name in other components.\n\n<KawaikoNote variant=\"question\" title=\"Why do we need Scoped CSS?\">\n\nIn large applications, different components may use the same class names.\\\nWithout Scoped CSS, styles could unintentionally affect other components.\\\nBy isolating styles per component, you can style safely!\n\n</KawaikoNote>\n\n## How It Works\n\nScoped CSS is implemented through these steps:\n\n1. **Generate scope ID**: Create a unique ID for each component\n2. **Transform template**: Add `data-v-xxx` attribute to elements\n3. **Transform styles**: Add `[data-v-xxx]` to selectors\n\n### Transformation Example\n\n```vue\n<!-- Input -->\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\n```html\n<!-- Output (HTML) -->\n<p class=\"message\" data-v-7ba5bd90>Hello</p>\n\n<!-- Output (CSS) -->\n<style>\n.message[data-v-7ba5bd90] {\n  color: red;\n}\n</style>\n```\n\n## Generating Scope ID\n\nGenerate a unique ID for each component. Usually uses a hash of the file path.\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nimport { createHash } from 'crypto'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  }\n\n  // Generate scope ID\n  descriptor.id = createHash('sha256')\n    .update(filename + source)\n    .digest('hex')\n    .slice(0, 8)\n\n  // ... rest of parsing\n}\n```\n\n## Extending SFCStyleBlock\n\nAdd scoped information to the style block.\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\"\n  scoped?: boolean  // Added\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  // ...\n  node.props.forEach((p) => {\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      attrs[p.name] = p.value ? p.value.content || true : true\n      if (type === \"style\") {\n        if (p.name === \"scoped\") {\n          (block as SFCStyleBlock).scoped = true\n        }\n      }\n    }\n  })\n  return block\n}\n```\n\n## Template Transformation\n\nAdd the scopeId attribute to elements during template compilation.\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper, scopeId } = context\n  const { tag, props, children } = node\n\n  // Add scopeId to props if present\n  let propsWithScope = props\n  if (scopeId) {\n    const scopeIdProp = `\"data-v-${scopeId}\": \"\"`\n    if (props) {\n      // Merge with existing props\n      propsWithScope = `{ ...${props}, ${scopeIdProp} }`\n    } else {\n      propsWithScope = `{ ${scopeIdProp} }`\n    }\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`)\n  genNodeList(genNullableArgs([tag, propsWithScope, children]), context)\n  push(`)`)\n}\n```\n\n## Style Transformation\n\nAdd scope attribute selectors to CSS selectors.\n\n```ts\n// packages/compiler-sfc/src/compileStyle.ts\n\nimport postcss from 'postcss'\n\nexport interface SFCStyleCompileOptions {\n  source: string\n  filename: string\n  id: string\n  scoped?: boolean\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): string {\n  const { source, id, scoped } = options\n\n  if (!scoped) {\n    return source\n  }\n\n  // Transform selectors using PostCSS\n  const result = postcss([scopedPlugin(id)]).process(source, { from: undefined })\n  return result.css\n}\n\nfunction scopedPlugin(id: string) {\n  const scopeId = `data-v-${id}`\n\n  return {\n    postcssPlugin: 'vue-sfc-scoped',\n    Rule(rule) {\n      // Add [data-v-xxx] to selectors\n      rule.selectors = rule.selectors.map((selector) => {\n        return `${selector}[${scopeId}]`\n      })\n    },\n  }\n}\n```\n\n## Vite Plugin Integration\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/main.ts\n\nasync function genStyleCode(descriptor: SFCDescriptor): Promise<string> {\n  let stylesCode = ``\n\n  for (let i = 0; i < descriptor.styles.length; i++) {\n    const style = descriptor.styles[i]\n    const src = descriptor.filename\n    const scoped = style.scoped ? '&scoped=true' : ''\n    const query = `?chibivue&type=style&index=${i}${scoped}&lang.css`\n    const styleRequest = src + query\n    stylesCode += `\\nimport ${JSON.stringify(styleRequest)}`\n  }\n\n  return stylesCode\n}\n\n// Compile styles in Vite plugin's load\nload(id) {\n  const { filename, query } = parseChibiVueRequest(id)\n  if (query.chibivue && query.type === \"style\") {\n    const descriptor = getDescriptor(filename, options)!\n    const style = descriptor.styles[query.index!]\n\n    if (query.scoped) {\n      return {\n        code: compileStyle({\n          source: style.content,\n          filename,\n          id: descriptor.id,\n          scoped: true,\n        })\n      }\n    }\n\n    return { code: style.content }\n  }\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"The Power of PostCSS!\">\n\nWe use PostCSS for style transformation.\\\nPostCSS is a tool that can handle CSS as an AST, making selector transformation easy.\\\nVue.js also uses PostCSS internally!\n\n</KawaikoNote>\n\n## Testing\n\n```vue\n<!-- ComponentA.vue -->\n<template>\n  <p class=\"text\">Component A</p>\n</template>\n\n<style scoped>\n.text {\n  color: red;\n}\n</style>\n```\n\n```vue\n<!-- ComponentB.vue -->\n<template>\n  <p class=\"text\">Component B</p>\n</template>\n\n<style scoped>\n.text {\n  color: blue;\n}\n</style>\n```\n\nBoth components use the same class name `.text`, but they display in different colors.\n\n## Special Selectors\n\nScoped CSS supports several special selectors.\n\n### :deep() Selector\n\nUsed when you want to style child components.\n\n```vue\n<style scoped>\n:deep(.child-class) {\n  color: blue;\n}\n</style>\n```\n\nTransformed output:\n\n```css\n[data-v-xxx] .child-class {\n  color: blue;\n}\n```\n\n### ::v-slotted() Selector\n\nApplies styles to slotted content.\n\n```vue\n<style scoped>\n::v-slotted(.slot-content) {\n  font-weight: bold;\n}\n</style>\n```\n\nTransformed output:\n\n```css\n.slot-content[data-v-xxx-s] {\n  font-weight: bold;\n}\n```\n\nThe `-s` suffix stands for \"slotted\".\nSince slotted content comes from the parent component,\na special slotted scope ID is used instead of the regular scope ID.\n\n### :global() Selector\n\nDefines global styles within a scoped style block.\n\n```vue\n<style scoped>\n:global(.global-class) {\n  margin: 0;\n}\n</style>\n```\n\nTransformed output:\n\n```css\n.global-class {\n  margin: 0;\n}\n```\n\n## Dynamic Styles with v-bind()\n\nYou can use component state in CSS.\n\n```vue\n<script setup>\nimport { ref } from 'vue'\nconst color = ref('red')\n</script>\n\n<style scoped>\n.text {\n  color: v-bind(color);\n}\n</style>\n```\n\nTransformed output:\n\n```css\n.text[data-v-xxx] {\n  color: var(--xxx-color);\n}\n```\n\n`v-bind()` is converted to a CSS custom property (CSS variable).\nAt runtime, the CSS variable value is set as an inline style on the component.\n\n### Using Complex Expressions\n\nYou can use complex expressions by wrapping them in quotes.\n\n```vue\n<style scoped>\n.box {\n  width: v-bind('size + \"px\"');\n  background: v-bind('theme.colors.primary');\n}\n</style>\n```\n\n<KawaikoNote variant=\"warning\" title=\"Performance Considerations for v-bind()\">\n\n`v-bind()` is a convenient feature, but it has performance implications:\n\n- Each `v-bind()` is set as a CSS custom property in inline styles\n- Style recalculation is triggered every time the value changes\n- For frequently changing values, using inline styles directly may be more efficient\n\nFor animations or frequent updates, consider using inline styles or CSS animations instead of `v-bind()`.\n\n</KawaikoNote>\n\n## Future Enhancements\n\nThese features could also be considered:\n\n- **CSS Modules**: Automatic class name generation\n- **CSS-in-JS Integration**: Enhanced dynamic styling\n\n<KawaikoNote variant=\"base\" title=\"Try Implementing It!\">\n\nUse the concepts explained in this chapter to implement Scoped CSS yourself!\\\nIt's also a great opportunity to learn how to use PostCSS.\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/040_scoped_css)\n\n## Summary\n\n- Scoped CSS isolates styles per component\n- Generate a unique scopeId and apply to template and styles\n- Template gets `data-v-xxx` attribute, CSS gets `[data-v-xxx]` selector\n- Use PostCSS to transform selectors\n\n## References\n\n- [Vue.js - Scoped CSS](https://vuejs.org/api/sfc-css-features.html#scoped-css) - Vue Official Documentation\n- [PostCSS](https://postcss.org/) - CSS Transformation Tool\n"
  },
  {
    "path": "book/online-book/src/60-basic-sfc-compiler/050-props-destructure.md",
    "content": "# Supporting Props Destructure\n\n::: info About this chapter\nThis chapter explains how to implement Vue 3.5's Reactive Props Destructure feature.\\\nLearn how to maintain reactivity while destructuring props.\n:::\n\n## What is Reactive Props Destructure?\n\nStarting from Vue 3.5, you can destructure the return value of `defineProps` in `<script setup>`.\n\n```vue\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\nThis feature makes accessing props simpler.\n\n<KawaikoNote variant=\"question\" title=\"Why is special handling needed?\">\n\nIn regular JavaScript, destructuring an object copies values and breaks the connection to the original object.\\\nHowever, Vue's props need to be reactive.\\\nThe compiler transforms destructured access to `__props.xxx` access to maintain reactivity!\n\n</KawaikoNote>\n\n## How It Works\n\nProps destructure is implemented through these steps:\n\n1. **Pattern detection**: Detect `const { ... } = defineProps(...)`\n2. **Binding registration**: Register each destructured property as `PROPS`\n3. **Default value handling**: Transform default values into `withDefaults` equivalent\n4. **Code transformation**: Transform props access to `__props.xxx`\n\n### Transformation Example\n\n```vue\n<!-- Input -->\n<script setup>\nconst { count, message = 'hello' } = defineProps({\n  count: Number,\n  message: String\n})\n\nconsole.log(count, message)\n</script>\n```\n\n```ts\n// Output\nexport default {\n  props: {\n    count: Number,\n    message: { type: String, default: 'hello' }\n  },\n  setup(__props) {\n    console.log(__props.count, __props.message)\n\n    return (_ctx) => {\n      // ...\n    }\n  }\n}\n```\n\n## Detecting Destructure Patterns\n\nDetect if `defineProps` return value is assigned to an `ObjectPattern` (destructure pattern).\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\ninterface PropsDestructureBindings {\n  [key: string]: {\n    local: string      // Local variable name\n    default?: string   // Default value\n  }\n}\n\nlet propsDestructuredBindings: PropsDestructureBindings = Object.create(null)\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  propsRuntimeDecl = node.arguments[0]\n\n  // Handle destructure pattern\n  if (declId && declId.type === \"ObjectPattern\") {\n    processPropsDestructure(declId)\n  } else if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## Processing Destructure\n\nExtract each property from `ObjectPattern` and register as bindings.\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"ObjectProperty\") {\n      const key = prop.key\n      const value = prop.value\n\n      // Get property name\n      let propKey: string\n      if (key.type === \"Identifier\") {\n        propKey = key.name\n      } else if (key.type === \"StringLiteral\") {\n        propKey = key.value\n      } else {\n        continue\n      }\n\n      // Process local variable name and default value\n      let local: string\n      let defaultValue: string | undefined\n\n      if (value.type === \"Identifier\") {\n        // const { count } = defineProps(...)\n        local = value.name\n      } else if (value.type === \"AssignmentPattern\") {\n        // const { count = 0 } = defineProps(...)\n        if (value.left.type === \"Identifier\") {\n          local = value.left.name\n          defaultValue = scriptSetup!.content.slice(\n            value.right.start!,\n            value.right.end!\n          )\n        } else {\n          continue\n        }\n      } else {\n        continue\n      }\n\n      // Register binding\n      propsDestructuredBindings[propKey] = { local, default: defaultValue }\n      bindingMetadata[local] = BindingTypes.PROPS\n    }\n  }\n}\n```\n\n## Default Value Handling\n\nWhen default values are specified in destructure, merge them into props definition.\n\n```ts\nfunction genRuntimeProps(): string | undefined {\n  if (!propsRuntimeDecl) return undefined\n\n  let propsString = scriptSetup!.content.slice(\n    propsRuntimeDecl.start!,\n    propsRuntimeDecl.end!\n  )\n\n  // Merge default values if present\n  const defaults: Record<string, string> = {}\n  for (const key in propsDestructuredBindings) {\n    const binding = propsDestructuredBindings[key]\n    if (binding.default) {\n      defaults[key] = binding.default\n    }\n  }\n\n  if (Object.keys(defaults).length > 0) {\n    // Process equivalent to withDefaults\n    propsString = mergeDefaults(propsString, defaults)\n  }\n\n  return propsString\n}\n\nfunction mergeDefaults(\n  propsString: string,\n  defaults: Record<string, string>\n): string {\n  // Actual implementation manipulates AST to merge defaults\n  // Simplified example here\n  const ast = parseExpression(propsString)\n  // ... merge default values\n  return generate(ast).code\n}\n```\n\n## Transforming Props Access\n\nTransform access to destructured variables into `__props.xxx` in templates and scripts.\n\n```ts\nfunction processPropsAccess(source: string): string {\n  const s = new MagicString(source)\n\n  // Walk identifiers and transform\n  walk(scriptSetupAst, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const binding = propsDestructuredBindings[node.name]\n        if (binding && binding.local === node.name) {\n          // Transform to props access\n          s.overwrite(node.start!, node.end!, `__props.${node.name}`)\n        }\n      }\n    }\n  })\n\n  return s.toString()\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"Compiler Magic!\">\n\nDestructuring normally loses reactivity in JavaScript,\\\nbut the compiler transforms it to `__props.xxx` access,\\\nallowing you to use destructure syntax as syntactic sugar!\n\n</KawaikoNote>\n\n## Rest Pattern Support\n\nSupport for `...rest` patterns is also possible.\n\n```vue\n<script setup>\nconst { id, ...attrs } = defineProps(['id', 'class', 'style'])\n</script>\n```\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"RestElement\") {\n      // Handle rest pattern\n      if (prop.argument.type === \"Identifier\") {\n        const restName = prop.argument.name\n        // rest requires special handling\n        // Actually uses computed to get remaining props\n        bindingMetadata[restName] = BindingTypes.SETUP_REACTIVE_CONST\n      }\n    }\n    // ...\n  }\n}\n```\n\n## Testing\n\n```vue\n<!-- Parent.vue -->\n<script setup>\nimport { ref } from 'chibivue'\nimport Child from './Child.vue'\n\nconst count = ref(0)\nconst message = ref('Hello')\n</script>\n\n<template>\n  <Child :count=\"count\" :message=\"message\" />\n  <button @click=\"count++\">Increment</button>\n</template>\n```\n\n```vue\n<!-- Child.vue -->\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n\n// count and message are transformed to __props.count, __props.message\nconsole.log(count, message)\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\n## Future Enhancements\n\nThese features could also be considered:\n\n- **Alias support**: Support for `const { count: c } = defineProps(...)`\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/050_props_destructure)\n\n## Summary\n\n- Props Destructure was introduced in Vue 3.5\n- Detect destructure patterns and register each property as `PROPS` binding\n- Default values are merged into props definition\n- Transform variable access to `__props.xxx` to maintain reactivity\n\n## References\n\n- [Vue.js - Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure) - Vue Official Documentation\n- [RFC - Reactive Props Destructure](https://github.com/vuejs/rfcs/discussions/502) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/60-basic-sfc-compiler/060-type-based-macros.md",
    "content": "# Type-based defineProps / defineEmits\n\n::: info About this chapter\nThis chapter explains how to implement `defineProps` and `defineEmits` using TypeScript type arguments.\\\nLearn how to generate runtime definitions from type definitions.\n:::\n\n## What are Type-based Declarations?\n\nIn Vue 3, you can declare `defineProps` and `defineEmits` using TypeScript generics.\n\n```vue\n<script setup lang=\"ts\">\n// Type-based defineProps\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n\n// Type-based defineEmits\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"Why are type-based declarations convenient?\">\n\nRuntime declarations use `Number`, `String`, etc.,\\\nbut with type-based declarations, you can use TypeScript's type system directly!\\\nIDE completion and error checking become more powerful.\n\n</KawaikoNote>\n\n## How It Works\n\nType-based macros are processed through these steps:\n\n1. **Type argument detection**: Detect generics in `defineProps<T>()`\n2. **Type parsing**: Parse TypeScript type definitions\n3. **Runtime definition generation**: Generate runtime props/emits from types\n4. **Code output**: Output as regular runtime declarations\n\n### Transformation Example\n\n```vue\n<!-- Input -->\n<script setup lang=\"ts\">\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n</script>\n```\n\n```ts\n// Output\nexport default {\n  props: {\n    count: { type: Number, required: true },\n    message: { type: String, required: false }\n  },\n  setup(__props) {\n    // ...\n  }\n}\n```\n\n## Detecting Type Arguments\n\nDetect if `defineProps` or `defineEmits` has type arguments.\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nlet propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  // Check type arguments\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    if (typeArg) {\n      propsTypeDecl = resolveTypeElements(typeArg)\n    }\n  } else {\n    // Runtime declaration\n    propsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n```\n\n## Parsing Types\n\nParse TypeScript type literals to extract property information.\n\n```ts\ninterface PropTypeData {\n  type: string[]      // Array of types (for Union support)\n  required: boolean   // Whether required\n}\n\nfunction extractPropsFromType(\n  typeDecl: TSTypeLiteral | TSInterfaceBody\n): Record<string, PropTypeData> {\n  const props: Record<string, PropTypeData> = {}\n\n  const members = typeDecl.type === \"TSTypeLiteral\"\n    ? typeDecl.members\n    : typeDecl.body\n\n  for (const member of members) {\n    if (member.type === \"TSPropertySignature\") {\n      const key = member.key\n      if (key.type !== \"Identifier\") continue\n\n      const propName = key.name\n      const isOptional = !!member.optional\n\n      // Parse type\n      const types = member.typeAnnotation\n        ? resolveType(member.typeAnnotation.typeAnnotation)\n        : [\"null\"]\n\n      props[propName] = {\n        type: types,\n        required: !isOptional\n      }\n    }\n  }\n\n  return props\n}\n```\n\n## Converting Types to Constructors\n\nConvert TypeScript types to JavaScript constructors.\n\n```ts\nfunction resolveType(node: TSType): string[] {\n  switch (node.type) {\n    case \"TSStringKeyword\":\n      return [\"String\"]\n\n    case \"TSNumberKeyword\":\n      return [\"Number\"]\n\n    case \"TSBooleanKeyword\":\n      return [\"Boolean\"]\n\n    case \"TSArrayType\":\n      return [\"Array\"]\n\n    case \"TSFunctionType\":\n      return [\"Function\"]\n\n    case \"TSObjectKeyword\":\n    case \"TSTypeLiteral\":\n      return [\"Object\"]\n\n    case \"TSUnionType\":\n      // Union types return multiple constructors\n      const types: string[] = []\n      for (const t of node.types) {\n        // Exclude null/undefined\n        if (t.type === \"TSNullKeyword\" || t.type === \"TSUndefinedKeyword\") {\n          continue\n        }\n        types.push(...resolveType(t))\n      }\n      return types\n\n    case \"TSTypeReference\":\n      // Custom types and references\n      if (node.typeName.type === \"Identifier\") {\n        const name = node.typeName.name\n        // Built-in type mapping\n        if (name === \"Array\") return [\"Array\"]\n        if (name === \"Function\") return [\"Function\"]\n        if (name === \"Object\") return [\"Object\"]\n        // Others as-is\n        return [name]\n      }\n      return [\"Object\"]\n\n    default:\n      return [\"null\"]\n  }\n}\n```\n\n## Generating Runtime Definition\n\nGenerate runtime props definition from parsed type information.\n\n```ts\nfunction genRuntimePropsFromType(\n  propsDecl: Record<string, PropTypeData>\n): string {\n  const props: string[] = []\n\n  for (const [key, { type, required }] of Object.entries(propsDecl)) {\n    const typeStr = type.length === 1\n      ? type[0]\n      : `[${type.join(\", \")}]`\n\n    if (required) {\n      props.push(`${key}: { type: ${typeStr}, required: true }`)\n    } else {\n      props.push(`${key}: { type: ${typeStr}, required: false }`)\n    }\n  }\n\n  return `{ ${props.join(\", \")} }`\n}\n```\n\n## defineEmits Type Processing\n\n`defineEmits` processes type arguments similarly.\n\n```ts\nlet emitsTypeDecl: TSFunctionType[] | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    emitsTypeDecl = resolveEmitsTypeElements(typeArg)\n  } else {\n    emitsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n\nfunction resolveEmitsTypeElements(\n  typeArg: TSType\n): TSFunctionType[] | undefined {\n  // Function overload format\n  if (typeArg.type === \"TSTypeLiteral\") {\n    return typeArg.members\n      .filter((m): m is TSCallSignatureDeclaration =>\n        m.type === \"TSCallSignatureDeclaration\"\n      )\n      .map(m => m as unknown as TSFunctionType)\n  }\n  return undefined\n}\n```\n\n## Generating emits Runtime Definition\n\n```ts\nfunction genRuntimeEmitsFromType(\n  emitsDecl: TSFunctionType[]\n): string {\n  const events: string[] = []\n\n  for (const sig of emitsDecl) {\n    // First argument is event name\n    const firstParam = sig.parameters?.[0]\n    if (firstParam?.type === \"Identifier\" && firstParam.typeAnnotation) {\n      const typeAnn = firstParam.typeAnnotation.typeAnnotation\n      if (typeAnn.type === \"TSLiteralType\" &&\n          typeAnn.literal.type === \"StringLiteral\") {\n        events.push(`\"${typeAnn.literal.value}\"`)\n      }\n    }\n  }\n\n  return `[${events.join(\", \")}]`\n}\n```\n\n### Transformation Example\n\n```vue\n<!-- Input -->\n<script setup lang=\"ts\">\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n```ts\n// Output\nexport default {\n  emits: ['change', 'update'],\n  setup(__props, { emit }) {\n    // ...\n  }\n}\n```\n\n## withDefaults Support\n\nTo specify default values with type-based props, use `withDefaults`.\n\n```vue\n<script setup lang=\"ts\">\ninterface Props {\n  count: number\n  message?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  message: 'default message'\n})\n</script>\n```\n\n```ts\nconst WITH_DEFAULTS = \"withDefaults\"\n\nfunction processWithDefaults(node: Node): boolean {\n  if (!isCallOf(node, WITH_DEFAULTS)) {\n    return false\n  }\n\n  const [propsCall, defaultsArg] = node.arguments\n\n  // Process defineProps\n  if (isCallOf(propsCall, DEFINE_PROPS)) {\n    processDefineProps(propsCall)\n  }\n\n  // Save default values\n  if (defaultsArg) {\n    propsDefaults = defaultsArg\n  }\n\n  return true\n}\n```\n\n## Testing\n\n```vue\n<!-- TypedComponent.vue -->\n<script setup lang=\"ts\">\ninterface Props {\n  id: number\n  name: string\n  active?: boolean\n}\n\ninterface Emits {\n  (e: 'select', id: number): void\n  (e: 'update', name: string): void\n}\n\nconst props = defineProps<Props>()\nconst emit = defineEmits<Emits>()\n\nfunction handleClick() {\n  emit('select', props.id)\n}\n</script>\n\n<template>\n  <div @click=\"handleClick\">\n    {{ name }} ({{ active ? 'active' : 'inactive' }})\n  </div>\n</template>\n```\n\n## Future Enhancements\n\nThese features could also be considered:\n\n- **Interface references**: Referencing types defined in other files\n- **Mapped Types**: Transform types like `Partial<T>`\n- **Generic components**: Components with generic type parameters\n- **Type-only imports**: Processing `import type`\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/060_type_based_macros)\n\n## Summary\n\n- Type-based defineProps/defineEmits use TypeScript type arguments\n- Compiler parses types and generates runtime definitions\n- TypeScript types are mapped to JavaScript constructors\n- Default values can be specified with withDefaults\n\n## References\n\n- [Vue.js - TypeScript with Composition API](https://vuejs.org/guide/typescript/composition-api.html) - Vue Official Documentation\n- [Vue.js - Type-only props/emit declarations](https://vuejs.org/api/sfc-script-setup.html#type-only-props-emit-declarations) - Vue Official Documentation\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/010-plugins/010-router.md",
    "content": "# Router\n\n## What is a Router?\n\nIn Single Page Applications (SPAs), we need to display different components based on the URL. In the Vue.js ecosystem, Vue Router provides this functionality.\n\n<KawaikoNote variant=\"question\" title=\"SPA Routing?\">\n\nIn traditional websites, a new HTML page was fetched from the server every time the URL changed.\nIn SPAs, page transitions are handled by JavaScript, updating the screen without server requests.\nThis is called \"client-side routing.\"\n\n</KawaikoNote>\n\nIn this chapter, we'll implement basic Vue Router functionality as chibivue-router.\n\n## Package Structure\n\nchibivue-router is provided in the `@extensions/chibivue-router` package.\n\n```\n@extensions/chibivue-router/src/\n├── index.ts              # Exports\n├── router.ts             # Main router logic\n├── history.ts            # History API wrapper\n├── RouterView.ts         # RouterView component\n├── useApi.ts             # Composition API hooks\n├── injectionSymbols.ts   # Dependency Injection keys\n└── types/\n    └── index.ts          # Type definitions\n```\n\n## Type Definitions\n\n### RouteLocationNormalizedLoaded\n\nA type representing current route information.\n\n```ts\n// types/index.ts\nexport interface RouteLocationNormalizedLoaded {\n  fullPath: string;\n  component: any;\n}\n```\n\n### RouteRecord\n\nA type representing route definitions.\n\n```ts\n// router.ts\nexport interface RouteRecord {\n  path: string;\n  component: any;\n}\n```\n\n### Router Interface\n\nDefines the router's public API.\n\n```ts\n// router.ts\nexport interface Router {\n  install(app: App): void;\n  push(to: string): void;\n  replace(to: string): void;\n}\n```\n\n## History API Abstraction\n\nWrapping the browser's History API to make it easier to use from the router.\n\n### RouterHistory Interface\n\n```ts\n// history.ts\nexport interface RouterHistory {\n  location: Location;\n  push(to: string): void;\n  replace(to: string): void;\n  go(delta: number, triggerListeners?: boolean): void;\n}\n```\n\n### createWebHistory Function\n\n```ts\n// history.ts\nexport const createWebHistory = (): RouterHistory => {\n  return {\n    location: window.location,\n    push(to: string) {\n      window.history.pushState({}, \"\", to);\n    },\n    replace(to: string) {\n      window.history.replaceState({}, \"\", to);\n    },\n    go(delta: number, triggerListeners?: boolean) {\n      window.history.go(delta);\n    },\n  };\n};\n```\n\nKey points:\n- `pushState`: Adds a new entry to the history (allows going back with the back button)\n- `replaceState`: Replaces the current history entry (doesn't remain in history)\n- `go`: Navigates forward or backward in history\n\n<KawaikoNote variant=\"funny\" title=\"pushState vs replaceState\">\n\nThink of `pushState` as \"adding a new book to the bookshelf.\"\n`replaceState` is like \"replacing the book you're currently reading with another one.\"\nThe back button is like \"going back to the book you were reading before.\"\n\n</KawaikoNote>\n\n## Dependency Injection Keys\n\nDefining keys for sharing router-related values via provide/inject.\n\n```ts\n// injectionSymbols.ts\nimport type { ComputedRef, InjectionKey, Ref } from \"@chibivue/runtime-core\";\nimport type { Router } from \"./router\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\n\n// The router itself\nexport const routerKey = Symbol() as InjectionKey<Router>;\n\n// Current route (wrapped in computed)\nexport const routeLocationKey = Symbol() as InjectionKey<\n  ComputedRef<RouteLocationNormalizedLoaded>\n>;\n\n// Route for RouterView (Ref)\nexport const routerViewLocationKey = Symbol() as InjectionKey<\n  Ref<RouteLocationNormalizedLoaded>\n>;\n```\n\nReasons for having three separate keys:\n1. `routerKey`: For accessing navigation methods (`push`, `replace`)\n2. `routeLocationKey`: For getting current route info with `useRoute()` (reactive via computed)\n3. `routerViewLocationKey`: For RouterView component to determine which component to display\n\n## createRouter Implementation\n\n### Route Resolution\n\n```ts\n// router.ts\nconst resolve = (to: string) => {\n  const route = options.routes.find((route) => route.path === to);\n  return {\n    fullPath: to,\n    component: route?.component ?? null,\n  };\n};\n```\n\nThe current implementation only supports exact matching. Vue Router's actual implementation also supports parameters (`/user/:id`) and regular expressions.\n\n### State Management\n\n```ts\n// router.ts\nconst currentRoute = ref<RouteLocationNormalizedLoaded>({\n  fullPath: routerHistory.location.pathname,\n  component: resolve(routerHistory.location.pathname).component,\n});\n```\n\nCurrent route information is managed with `ref`. This allows RouterView to automatically re-render when the route changes.\n\n### Navigation Methods\n\n```ts\n// router.ts\nfunction push(to: string) {\n  routerHistory.push(to);\n  currentRoute.value = resolve(to);\n}\n\nfunction replace(to: string) {\n  routerHistory.replace(to);\n  currentRoute.value = resolve(to);\n}\n```\n\nChanges the URL and simultaneously updates the reactive state.\n\n### Plugin Installation\n\n```ts\n// router.ts\ninstall(app: App) {\n  const router = this;\n\n  // Register RouterView component globally\n  app.component(\"RouterView\", RouterViewImpl);\n\n  // Create reactive route information\n  const reactiveRoute = computed(() => currentRoute.value);\n\n  // Provide values\n  app.provide(routerKey, router);\n  app.provide(routeLocationKey, reactive(reactiveRoute));\n  app.provide(routerViewLocationKey, currentRoute);\n}\n```\n\nWhen `app.use(router)` is called, this `install` method is executed.\n\n## RouterView Component\n\nDisplays the component corresponding to the current route.\n\n```ts\n// RouterView.ts\nimport { type ComponentOptions, Fragment, h, inject } from \"chibivue\";\nimport { routerViewLocationKey } from \"./injectionSymbols\";\n\nexport const RouterViewImpl: ComponentOptions = {\n  name: \"RouterView\",\n  setup() {\n    const injectedRoute = inject(routerViewLocationKey)!;\n\n    return () => {\n      const ViewComponent = injectedRoute.value.component;\n\n      // Wrap in Fragment for rendering\n      const component = h(Fragment, [\n        h(ViewComponent, { key: injectedRoute.value.fullPath }),\n      ]);\n\n      return component;\n    };\n  },\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"The key attribute is important!\">\n\nBy specifying `fullPath` as the `key`, the component is completely remounted whenever the route changes.\nWithout this, the same component would be reused and `setup` wouldn't be re-executed.\n\n</KawaikoNote>\n\nThe reason for wrapping in Fragment is to ensure proper patch children behavior.\n\n## Composition API Hooks\n\n### useRouter\n\nGets the router instance.\n\n```ts\n// useApi.ts\nexport function useRouter(): Router {\n  return inject(routerKey)!;\n}\n```\n\nUsage:\n```ts\nconst router = useRouter()\nrouter.push('/about')\n```\n\n### useRoute\n\nGets current route information.\n\n```ts\n// useApi.ts\nexport function useRoute(): ComputedRef<RouteLocationNormalizedLoaded> {\n  return inject(routeLocationKey)!;\n}\n```\n\nUsage:\n```ts\nconst route = useRoute()\nconsole.log(route.value.fullPath) // '/about'\n```\n\n## Usage Example\n\n### Router Configuration\n\n```ts\n// router.ts\nimport { createRouter, createWebHistory } from 'chibivue-router'\nimport Home from './pages/Home.vue'\nimport About from './pages/About.vue'\nimport Contact from './pages/Contact.vue'\n\nexport const router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '/', component: Home },\n    { path: '/about', component: About },\n    { path: '/contact', component: Contact },\n  ],\n})\n```\n\n### Registering with Application\n\n```ts\n// main.ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\nimport { router } from './router'\n\nconst app = createApp(App)\napp.use(router)\napp.mount('#app')\n```\n\n### Using in Templates\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { useRouter } from 'chibivue-router'\n\nconst router = useRouter()\n</script>\n\n<template>\n  <header>\n    <nav>\n      <button @click=\"router.push('/')\">Home</button>\n      <button @click=\"router.push('/about')\">About</button>\n      <button @click=\"router.push('/contact')\">Contact</button>\n    </nav>\n  </header>\n\n  <main>\n    <RouterView />\n  </main>\n</template>\n```\n\n## Processing Flow\n\n```\napp.use(router)\n  ↓\nrouter.install(app)\n  ├── app.component(\"RouterView\", RouterViewImpl)\n  ├── app.provide(routerKey, router)\n  ├── app.provide(routeLocationKey, ...)\n  └── app.provide(routerViewLocationKey, currentRoute)\n  ↓\nRouterView renders\n  ↓\ninject(routerViewLocationKey) gets currentRoute\n  ↓\nRender currentRoute.value.component\n\n--- Navigation ---\n\nrouter.push('/about')\n  ↓\nrouterHistory.push('/about')  ← URL change\n  ↓\ncurrentRoute.value = resolve('/about')  ← State update\n  ↓\nRouterView re-renders\n  ↓\nDisplay new component\n```\n\n## Future Extensions\n\nThe current implementation is minimal, but Vue Router has features like:\n\n1. **RouterLink component**: A navigation component wrapping `<a>` tags\n2. **Route parameters**: Dynamic segments like `/user/:id`\n3. **Query parameters**: Parsing `?key=value`\n4. **Navigation guards**: Hooks like `beforeEach`, `afterEach`\n5. **popstate event**: Handling browser back/forward buttons\n6. **Nested routes**: Defining child routes\n\n<KawaikoNote variant=\"surprise\" title=\"Implementation complete!\">\n\nWe've completed a simple router.\nWith about 100 lines of code, we've achieved SPA routing.\nThis should be a good starting point for understanding how Vue Router works.\n\n</KawaikoNote>\n\n## Summary\n\nThe chibivue-router implementation consists of:\n\n1. **History API wrapper**: Abstract browser history operations with `createWebHistory`\n2. **Reactive state management**: Manage current route with `ref`\n3. **Dependency Injection**: Share router information throughout the component tree via `provide/inject`\n4. **RouterView component**: Dynamically display components corresponding to the current route\n5. **Composition API hooks**: Easy access with `useRouter` and `useRoute`\n\nBy combining Vue's plugin system, provide/inject, and reactivity system, we've achieved client-side routing.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/010-plugins/020-preprocessors.md",
    "content": "# CSS Preprocessors\n\n## What are Preprocessors?\n\nCSS preprocessors are tools that transform extended CSS languages (SCSS, Less, Stylus, etc.) into standard CSS. These languages provide features like variables, nesting, mixins, and functions, making CSS writing more efficient.\n\n<KawaikoNote variant=\"question\" title=\"Why use preprocessors?\">\n\nPlain CSS has several limitations:\n- No variables (CSS custom properties were added later)\n- No nesting\n- Code reuse is difficult\n\nPreprocessors solve these problems and enable writing maintainable stylesheets.\n\n</KawaikoNote>\n\nIn Vue SFC, you can use preprocessors by specifying the `lang` attribute on the `<style>` block.\n\n```vue\n<style lang=\"scss\">\n$primary-color: #42b883;\n\n.container {\n  .title {\n    color: $primary-color;\n  }\n}\n</style>\n```\n\n## Supported Preprocessors\n\nVue/chibivue supports the following preprocessors:\n\n| Preprocessor | lang attribute | Features |\n|--------------|---------------|----------|\n| **SCSS** | `scss` | CSS-like syntax, variables, nesting, mixins |\n| **Sass** | `sass` | Indent-based syntax (no braces) |\n| **Less** | `less` | Variables (`@`), mixins, functions |\n| **Stylus** | `styl`, `stylus` | Flexible syntax, optional delimiters |\n\n## Type Definitions\n\n### StylePreprocessor\n\nA common interface for preprocessors.\n\n```ts\n// style/preprocessors.ts\nexport type StylePreprocessor = (\n  source: string,\n  map: RawSourceMap | undefined,\n  options: {\n    [key: string]: any;\n    additionalData?: string | ((source: string, filename: string) => string);\n    filename: string;\n  },\n  customRequire: (id: string) => any,\n) => StylePreprocessorResults;\n```\n\n### StylePreprocessorResults\n\nA type representing preprocessor results.\n\n```ts\nexport interface StylePreprocessorResults {\n  code: string;           // Transformed CSS\n  map?: object;          // Source map\n  errors: Error[];       // Error list\n  dependencies: string[]; // Dependency files (@import, etc.)\n}\n```\n\n`dependencies` is important. It enables tools like Vite to trigger rebuilds when files imported via `@import` in the preprocessor change.\n\n## Processing Flow\n\n```\nSFC file (.vue)\n    ↓\n[SFC Parser] - Detects <style lang=\"scss\">\n    ↓\n[compileStyle]\n    ↓\n1. Select preprocessor\n   processors[preprocessLang] → scss preprocessor\n    ↓\n2. Transform with preprocessor\n   SCSS/Sass/Less/Stylus → CSS\n    ↓\n3. PostCSS pipeline\n   ├── cssVarsPlugin (v-bind processing)\n   ├── trimPlugin (whitespace removal)\n   └── scopedPlugin (scoped CSS)\n    ↓\n4. Return result\n   { code, map, errors, dependencies }\n```\n\n## Preprocessor Implementations\n\n### SCSS Preprocessor\n\n```ts\n// style/preprocessors.ts\nconst scss: StylePreprocessor = (source, map, options, load = require) => {\n  // Dynamically load Dart Sass library\n  const nodeSass: typeof import(\"sass\") = load(\"sass\");\n  const { compileString, renderSync } = nodeSass;\n\n  // Apply additionalData (inject common variables, etc.)\n  const data = getSource(source, options.filename, options.additionalData);\n\n  let css: string;\n  let dependencies: string[];\n  let sourceMap: any;\n\n  try {\n    if (compileString) {\n      // New API (Sass 1.55.0+)\n      const result = compileString(data, {\n        ...options,\n        url: pathToFileURL(options.filename),\n        sourceMap: !!map,\n      });\n      css = result.css;\n      dependencies = result.loadedUrls.map((url) => fileURLToPath(url));\n      sourceMap = map ? result.sourceMap! : undefined;\n    } else {\n      // Legacy API (backward compatibility)\n      const result = renderSync({\n        ...options,\n        data,\n        file: options.filename,\n        outFile: options.filename,\n        sourceMap: !!map,\n      });\n      css = result.css.toString();\n      dependencies = result.stats.includedFiles;\n      sourceMap = map ? JSON.parse(result.map!.toString()) : undefined;\n    }\n\n    // Merge source maps\n    if (map) {\n      return {\n        code: css,\n        errors: [],\n        dependencies,\n        map: merge(map, sourceMap!),\n      };\n    }\n    return { code: css, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"API Compatibility\">\n\nSass has two APIs: old and new.\n`compileString` is the new API, and `renderSync` is the old API.\nSupporting both ensures compatibility with any Sass version.\n\n</KawaikoNote>\n\n### Sass Preprocessor\n\nSass uses the same engine as SCSS but uses indent-based syntax.\n\n```ts\nconst sass: StylePreprocessor = (source, map, options, load) =>\n  scss(\n    source,\n    map,\n    {\n      ...options,\n      indentedSyntax: true,  // Enable indented syntax\n    },\n    load,\n  );\n```\n\n### Less Preprocessor\n\n```ts\nconst less: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeLess = load(\"less\");\n\n  let result: any;\n  let error: Error | null = null;\n\n  // Less render is async, but syncImport: true makes it synchronous\n  nodeLess.render(\n    getSource(source, options.filename, options.additionalData),\n    { ...options, syncImport: true },\n    (err: Error | null, output: any) => {\n      error = err;\n      result = output;\n    },\n  );\n\n  if (error) return { code: \"\", errors: [error], dependencies: [] };\n\n  // Less returns dependencies via imports property\n  const dependencies = result.imports;\n\n  if (map) {\n    return {\n      code: result.css.toString(),\n      map: merge(map, result.map),\n      errors: [],\n      dependencies,\n    };\n  }\n\n  return {\n    code: result.css.toString(),\n    errors: [],\n    dependencies,\n  };\n};\n```\n\n### Stylus Preprocessor\n\n```ts\nconst styl: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeStylus = load(\"stylus\");\n\n  try {\n    const ref = nodeStylus(source, options);\n\n    // Configure source map\n    if (map) ref.set(\"sourcemap\", { inline: false, comment: false });\n\n    const result = ref.render();\n    const dependencies = ref.deps();  // Get dependencies\n\n    if (map) {\n      return {\n        code: result,\n        map: merge(map, ref.sourcemap),\n        errors: [],\n        dependencies,\n      };\n    }\n\n    return { code: result, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n## Injecting Common Styles with additionalData\n\nThe `additionalData` option allows you to inject common code into all style files.\n\n```ts\nfunction getSource(\n  source: string,\n  filename: string,\n  additionalData?: string | ((source: string, filename: string) => string),\n) {\n  if (!additionalData) return source;\n\n  // If function, generate dynamically\n  if (isFunction(additionalData)) {\n    return additionalData(source, filename);\n  }\n\n  // If string, prepend to source\n  return additionalData + source;\n}\n```\n\nExample usage (Vite config):\n\n```ts\n// vite.config.ts\nexport default defineConfig({\n  css: {\n    preprocessorOptions: {\n      scss: {\n        // Inject variables into all SCSS files\n        additionalData: `@import \"@/styles/variables.scss\";`,\n      },\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"funny\" title=\"Injecting Global Variables\">\n\n`additionalData` is like \"automatically copy-pasting to the beginning of every style file.\"\nIt saves you from having to import variables and mixins every time.\n\n</KawaikoNote>\n\n## Preprocessor Registration\n\n```ts\nexport type PreprocessLang = \"less\" | \"sass\" | \"scss\" | \"styl\" | \"stylus\";\n\nexport const processors: Record<PreprocessLang, StylePreprocessor> = {\n  less,\n  sass,\n  scss,\n  styl,\n  stylus: styl,  // alias\n};\n```\n\n## Integration in compileStyle\n\nPreprocessors are called within the `compileStyle` function.\n\n```ts\n// compileStyle.ts\nexport function doCompileStyle(options: SFCAsyncStyleCompileOptions) {\n  const {\n    filename,\n    id,\n    scoped = false,\n    trim = true,\n    preprocessLang,\n    // ...\n  } = options;\n\n  // Select preprocessor\n  const preprocessor = preprocessLang && processors[preprocessLang];\n\n  // Execute preprocessor if present\n  const preProcessedSource = preprocessor && preprocess(options, preprocessor);\n\n  // Get source map (from preprocessor or input)\n  const map = preProcessedSource ? preProcessedSource.map : options.inMap;\n\n  // CSS source (transformed or original)\n  const source = preProcessedSource ? preProcessedSource.code : options.source;\n\n  // Build PostCSS pipeline\n  const plugins = (postcssPlugins || []).slice();\n  plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));\n  if (trim) plugins.push(trimPlugin());\n  if (scoped) plugins.push(scopedPlugin(longId));\n\n  // Collect dependencies\n  const dependencies = new Set(\n    preProcessedSource ? preProcessedSource.dependencies : []\n  );\n\n  // Process with PostCSS\n  const result = postcss(plugins).process(source, postCSSOptions);\n\n  return {\n    code: result.css,\n    map: result.map?.toJSON(),\n    errors: [...errors],\n    dependencies,\n  };\n}\n```\n\n## Usage Examples\n\n### SCSS\n\n```vue\n<style lang=\"scss\">\n$primary: #42b883;\n$secondary: #35495e;\n\n.card {\n  background: $secondary;\n\n  .title {\n    color: $primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px rgba($secondary, 0.3);\n  }\n}\n</style>\n```\n\n### Less\n\n```vue\n<style lang=\"less\">\n@primary: #42b883;\n@secondary: #35495e;\n\n.card {\n  background: @secondary;\n\n  .title {\n    color: @primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px fade(@secondary, 30%);\n  }\n}\n</style>\n```\n\n### Stylus\n\n```vue\n<style lang=\"stylus\">\nprimary = #42b883\nsecondary = #35495e\n\n.card\n  background secondary\n\n  .title\n    color primary\n    font-size 1.5rem\n\n  &:hover\n    box-shadow 0 4px 8px rgba(secondary, 0.3)\n</style>\n```\n\n## Source Map Chaining\n\nBoth preprocessors and PostCSS generate source maps. We use the `merge-source-map` library to properly chain them.\n\n```\nSCSS source\n    ↓ [SCSS → CSS]\n    ↓ Source map A\nCSS\n    ↓ [PostCSS]\n    ↓ Source map B\nFinal CSS\n    ↓\nmerge(A, B) → Final source map\n```\n\nThis allows browser DevTools to show line numbers from the original SCSS/Less/Stylus files when debugging.\n\n<KawaikoNote variant=\"surprise\" title=\"Debugging made easier!\">\n\nWith source maps, when you wonder \"where did this CSS come from?\" in the browser,\nyou can see the exact location in the original SCSS file before transformation.\n\n</KawaikoNote>\n\n## Summary\n\nThe CSS preprocessor implementation consists of:\n\n1. **Common interface**: Abstract each preprocessor with the `StylePreprocessor` type\n2. **Dynamic loading**: Load preprocessors with `require()` or `customRequire`\n3. **additionalData**: Inject common styles (variables, mixins, etc.)\n4. **Dependency tracking**: Collect `@import`ed files for hot reload support\n5. **Source map chaining**: Merge preprocessor and PostCSS source maps\n6. **PostCSS integration**: Pass preprocessor output to PostCSS pipeline\n\nThe Vue/chibivue SFC compiler abstracts preprocessors, allowing users to use their preferred CSS language.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/010-plugins/020-store.md",
    "content": "# Store\n\n## What is a Store?\n\nAs applications grow larger, you often need to share state across multiple components. In the Vue.js ecosystem, Pinia provides this functionality.\n\nIn this chapter, we'll implement basic Pinia functionality as chibivue-store.\n\n### Why Do We Need a Library?\n\nIf you just want to share state across components, exporting `ref` and `computed` at module scope is sufficient:\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\n\nexport const count = ref(0);\nexport const doubleCount = computed(() => count.value * 2);\nexport const increment = () => count.value++;\n```\n\nThis works fine for CSR (Client-Side Rendering). However, it causes serious problems in SSR (Server-Side Rendering).\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\nIn SSR, you must be aware of \"**Cross-Request State Pollution**\".\n\nSince the server initializes modules only once, module-scoped state like above is **shared across all requests**.\nThis can lead to one user's state leaking to another user.\n\n</KawaikoNote>\n\nWith a state management library like Pinia, simply calling `useXxxStore()` inside setup automatically handles per-request state isolation.\n\n<KawaikoNote variant=\"info\" title=\"If You're Using Nuxt\">\n\nIf you're using Nuxt, it provides [useState](https://nuxt.com/docs/api/composables/use-state), an SSR-friendly composable for state management.\nFor simple state sharing, `useState` may be sufficient without introducing Pinia.\n\n</KawaikoNote>\n\nThis chapter covers basic CSR usage through to SSR hydration.\n\nFor more details on SSR, see the [SSR chapter](/90-web-application-essentials/020-ssr/010-create-ssr-app).\n\n## Package Structure\n\nchibivue-store is provided in the `@extensions/chibivue-store` package.\n\n```\n@extensions/chibivue-store/src/\n├── index.ts           # Exports\n├── createStore.ts     # Root store creation\n├── rootStore.ts       # Store interface and symbols\n└── store.ts           # defineStore implementation\n```\n\n## Type Definitions\n\n### StateTree\n\nThe type representing state held by a store.\n\n```ts\n// rootStore.ts\nexport type StateTree = Record<string | number | symbol, any>;\n```\n\n### Store Interface\n\nDefines the public API of the root store.\n\n```ts\n// rootStore.ts\nexport interface Store {\n  install: (app: App) => void;\n  use(plugin: StorePlugin): Store;\n  state: Ref<Record<string, StateTree>>;\n  _p: StorePlugin[];\n  _a: App | null;\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n- `install`: Installation method as a Vue plugin\n- `use`: Method to add plugins\n- `state`: Ref holding all store states (for SSR)\n- `_p`: Installed plugins\n- `_a`: App linked to this store\n- `_e`: EffectScope the store is attached to\n- `_s`: Map managing defined stores by ID\n\n### StoreInstance Interface\n\nDefines methods available on each store instance.\n\n```ts\n// store.ts\nexport interface StoreInstance<\n  Id extends string = string,\n  S extends StateTree = StateTree,\n  G extends _GettersTree<S> = _GettersTree<S>,\n  A = Record<string, (...args: any[]) => any>,\n> {\n  $id: Id;\n  $state: S;\n  $patch: (partialState: Partial<S> | ((state: S) => void)) => void;\n  $reset: () => void;\n}\n```\n\n- `$id`: Store identifier\n- `$state`: Store state (Options API style only)\n- `$patch`: Batch state update\n- `$reset`: Reset state to initial values (Options API style only)\n\n## Dependency Injection Key\n\nDefining a key for sharing the store via provide/inject.\n\n```ts\n// rootStore.ts\nimport type { InjectionKey } from \"chibivue\";\n\nexport const storeSymbol: InjectionKey<Store> = Symbol();\n```\n\nThis symbol is used to provide the store created by `createStore()` throughout the app.\n\n## createStore Implementation\n\nA function that creates the root store.\n\n```ts\n// createStore.ts\nimport { effectScope, markRaw, ref } from \"chibivue\";\nimport { type Store, setActiveStore, storeSymbol } from \"./rootStore\";\n\nexport function createStore(): Store {\n  const scope = effectScope();\n\n  const state = scope.run(() => ref({}))!;\n\n  let _p: StorePlugin[] = [];\n  let toBeInstalled: StorePlugin[] = [];\n\n  const store: Store = markRaw({\n    install(app) {\n      setActiveStore(store);\n      store._a = app;\n      app.provide(storeSymbol, store);\n      toBeInstalled.forEach((plugin) => _p.push(plugin));\n      toBeInstalled = [];\n    },\n\n    use(plugin) {\n      if (!this._a) {\n        toBeInstalled.push(plugin);\n      } else {\n        _p.push(plugin);\n      }\n      return this;\n    },\n\n    _p,\n    _a: null,\n    _e: scope,\n    _s: new Map(),\n    state,\n  });\n\n  return store;\n}\n```\n\nKey points:\n- `effectScope()` creates a detached scope to manage the store's lifecycle\n- `state` is `ref({})`, centrally managing all store states (for SSR)\n- `markRaw` prevents the store object itself from being made reactive\n- The `install` method calls `app.provide` to make the store available app-wide\n\n### Managing activeStore\n\n```ts\n// rootStore.ts\nexport let activeStore: Store | undefined;\nexport const setActiveStore = (store: Store | undefined): Store | undefined =>\n  (activeStore = store);\n\nexport const getActiveStore = (): Store | undefined => {\n  const store = hasInjectionContext() && inject(storeSymbol, null);\n\n  if (__DEV__ && !store && typeof window === \"undefined\") {\n    console.warn(\n      `[chibivue-store]: Store instance not found in context. ` +\n      `This falls back to the global activeStore which exposes you to ` +\n      `cross-request state pollution on the server.`,\n    );\n  }\n\n  return store || activeStore;\n};\n```\n\n`activeStore` is used when accessing stores from outside components (e.g., within other stores).\n\n`getActiveStore` uses `hasInjectionContext()` to check the injection context and warns in SSR environments when no context is found. This alerts developers to the risk of Cross-Request State Pollution.\n\n## defineStore Implementation\n\nA function for defining individual stores. Like Pinia, it supports two definition styles.\n\n### Composition API Style\n\n```ts\n// Composition API style (setup function)\nexport function defineStore<Id extends string, SS extends StateTree>(\n  id: Id,\n  setup: () => SS,\n): () => SS;\n```\n\nPass a `setup` function and define state using `ref` and `computed`.\n\n### Options API Style\n\n```ts\n// Options API style\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(options: StoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>;\n\n// Options API style (id as first argument)\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(\n  id: Id,\n  options: Omit<StoreOptions<Id, S, G, A>, \"id\">,\n): StoreDefinition<Id, S, G, A>;\n```\n\nDefine with an object containing `state`, `getters`, and `actions`.\n\n### StoreOptions Interface\n\n```ts\ninterface StoreOptions<Id extends string, S extends StateTree, G extends _GettersTree<S>, A> {\n  id: Id;\n  state?: () => S;\n  getters?: G & ThisType<S & { [K in keyof G]: ReturnType<G[K]> }>;\n  actions?: A & ThisType<S & A & { [K in keyof G]: ReturnType<G[K]> }>;\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"The Magic of ThisType\">\n\nUsing `ThisType` allows proper type inference for `this` inside `getters` and `actions`.\nFor example, in `actions` you can access state via `this.count` and getters via `this.doubleCount`.\n\n</KawaikoNote>\n\n### useStore Function Implementation\n\n```ts\nfunction useStore(outerStore?: Store | null) {\n  const currentInstance = getCurrentInstance();\n  let store = currentInstance && inject(storeSymbol);\n  if (store) setActiveStore(store);\n  store = outerStore ?? activeStore!;\n\n  if (!store._s.has(id)) {\n    if (setup) {\n      createSetupStore(id, setup, store);\n    } else if (options) {\n      createOptionsStore(id, options, store);\n    }\n  }\n\n  const _store = store!._s.get(id)!;\n  return _store;\n}\n```\n\nProcessing flow:\n1. Get component instance with `getCurrentInstance()`\n2. Get root store with `inject(storeSymbol)`\n3. If store doesn't exist, create with `createSetupStore` or `createOptionsStore`\n4. Return the created store\n\n### createSetupStore (for Composition API)\n\n```ts\nfunction createSetupStore<Id extends string>(id: Id, setup: () => StateTree, store: Store) {\n  const setupStore = setup();\n\n  const _store = reactive({\n    $id: id,\n    ...setupStore,\n    $patch(partialState: Partial<StateTree> | ((state: StateTree) => void)) {\n      if (typeof partialState === \"function\") {\n        partialState(setupStore);\n      } else {\n        for (const key in partialState) {\n          const value = setupStore[key];\n          if (isRef(value)) {\n            value.value = partialState[key];\n          } else {\n            setupStore[key] = partialState[key];\n          }\n        }\n      }\n    },\n    $reset() {\n      console.warn(`[$reset] is not available in setup stores.`);\n    },\n  });\n\n  store._s.set(id, _store);\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"$reset Limitation\">\n\nIn Composition API style, `$reset` is not available because initial state is not preserved.\nUse Options API style if you need `$reset`.\n\n</KawaikoNote>\n\n### createOptionsStore (for Options API)\n\n```ts\nfunction createOptionsStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(id: Id, options: Omit<StoreOptions<Id, S, G, A>, \"id\">, store: Store) {\n  const { state: stateFn, getters, actions } = options;\n\n  const initialState = stateFn ? stateFn() : ({} as S);\n  const state = reactive({ ...initialState }) as S;\n\n  // Create getters as computed properties\n  const computedGetters: Record<string, ComputedRef<unknown>> = {};\n  if (getters) {\n    for (const key in getters) {\n      const getter = getters[key];\n      computedGetters[key] = computed(() => getter.call(state, state));\n    }\n  }\n\n  // Bind actions to state\n  const boundActions: Record<string, (...args: any[]) => any> = {};\n  if (actions) {\n    for (const key in actions) {\n      const action = actions[key];\n      boundActions[key] = function (this: any, ...args: any[]) {\n        return action.apply(\n          { ...state, ...computedGetters, ...boundActions },\n          args,\n        );\n      };\n    }\n  }\n\n  const _store = reactive({\n    $id: id,\n    $state: state,\n    ...state,\n    ...computedGetters,\n    ...boundActions,\n    $patch(partialState: Partial<S> | ((state: S) => void)) { /* ... */ },\n    $reset() {\n      const newState = stateFn ? stateFn() : ({} as S);\n      for (const key in newState) {\n        (state as any)[key] = newState[key];\n      }\n    },\n  });\n\n  store._s.set(id, _store);\n}\n```\n\nKey points:\n- `state` is made reactive with `reactive`\n- `getters` are converted to `computed`\n- `actions` are bound to access state and getters\n- `$reset` re-executes the `state` function to restore initial values\n\n## Usage Examples\n\n### Composition API Style\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", () => {\n  // State\n  const count = ref(0);\n\n  // Getters (using computed)\n  const doubleCount = computed(() => count.value * 2);\n\n  // Actions\n  const increment = () => {\n    count.value++;\n  };\n\n  const reset = () => {\n    count.value = 0;\n  };\n\n  return {\n    count,\n    doubleCount,\n    increment,\n    reset,\n  };\n});\n```\n\n### Options API Style\n\n```ts\n// stores/counter.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", {\n  state: () => ({\n    count: 0,\n  }),\n\n  getters: {\n    doubleCount(state) {\n      return state.count * 2;\n    },\n    // Use this to access other getters\n    quadrupleCount() {\n      return this.doubleCount * 2;\n    },\n  },\n\n  actions: {\n    increment() {\n      this.count++;\n    },\n    // Async actions are also possible\n    async incrementAsync() {\n      await new Promise((resolve) => setTimeout(resolve, 1000));\n      this.count++;\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"funny\" title=\"Which Style to Choose?\">\n\n- **Composition API style**: More flexible, same syntax as regular components\n- **Options API style**: Clear structure, `$reset` available\n\nBoth provide equivalent functionality, choose based on your project's conventions.\n\n</KawaikoNote>\n\n### Registering with Application\n\n```ts\n// main.ts\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\nimport { createStore } from \"chibivue-store\";\n\nconst app = createApp(App);\napp.use(createStore());\napp.mount(\"#app\");\n```\n\n### Using in Components\n\n```vue\n<!-- Counter.vue -->\n<script setup>\nimport { useCounterStore } from \"../stores/counter\";\n\nconst counterStore = useCounterStore();\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ counterStore.count }}</p>\n    <p>Double: {{ counterStore.doubleCount }}</p>\n    <button @click=\"counterStore.increment\">Increment</button>\n  </div>\n</template>\n```\n\n## Using $patch\n\n`$patch` allows updating multiple state properties at once.\n\n### Object Form\n\n```ts\nconst store = useCounterStore();\n\n// Update multiple properties at once\nstore.$patch({\n  count: 10,\n});\n```\n\n### Function Form\n\n```ts\nconst store = useCounterStore();\n\n// Directly manipulate state\nstore.$patch((state) => {\n  state.count += 5;\n});\n```\n\n<KawaikoNote variant=\"warning\" title=\"Benefits of $patch\">\n\nBatching multiple state changes with `$patch` triggers reactivity only once, improving performance.\n\n</KawaikoNote>\n\n## Using $reset\n\nFor stores defined with Options API style, `$reset` resets state to initial values.\n\n```ts\nconst store = useCounterStore();\n\nstore.increment(); // count: 1\nstore.increment(); // count: 2\n\nstore.$reset(); // count: 0 (back to initial value)\n```\n\n## Processing Flow\n\n```\napp.use(createStore())\n  ↓\nstore.install(app)\n  ├── setActiveStore(store)\n  └── app.provide(storeSymbol, store)\n  ↓\nCall useCounterStore() in component\n  ↓\nuseStore()\n  ├── inject(storeSymbol) to get store\n  └── Check store._s.has(\"counter\")\n      ↓ (if not exists)\n      createSetupStore() or createOptionsStore()\n        ├── Execute setup() / state()\n        ├── Convert getters to computed\n        ├── Bind actions\n        └── store._s.set(\"counter\", result)\n  ↓\nReturn store._s.get(\"counter\")\n  ↓\nUse reactive state in component\n```\n\n## Multiple Stores\n\nYou can define and use multiple stores.\n\n```ts\n// stores/user.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useUserStore = defineStore(\"user\", {\n  state: () => ({\n    name: \"\",\n    isLoggedIn: false,\n  }),\n\n  actions: {\n    login(userName: string) {\n      this.name = userName;\n      this.isLoggedIn = true;\n    },\n    logout() {\n      this.$reset();\n    },\n  },\n});\n```\n\n```ts\n// stores/cart.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCartStore = defineStore(\"cart\", {\n  state: () => ({\n    items: [] as { id: number; name: string; price: number }[],\n  }),\n\n  getters: {\n    total(state) {\n      return state.items.reduce((sum, item) => sum + item.price, 0);\n    },\n    itemCount(state) {\n      return state.items.length;\n    },\n  },\n\n  actions: {\n    addItem(item: { id: number; name: string; price: number }) {\n      this.items.push(item);\n    },\n    clearCart() {\n      this.$reset();\n    },\n  },\n});\n```\n\n### Store Composition\n\nYou can use one store from within another.\n\n```ts\n// stores/checkout.ts\nimport { defineStore } from \"chibivue-store\";\nimport { useUserStore } from \"./user\";\nimport { useCartStore } from \"./cart\";\n\nexport const useCheckoutStore = defineStore(\"checkout\", {\n  actions: {\n    checkout() {\n      const userStore = useUserStore();\n      const cartStore = useCartStore();\n\n      if (!userStore.isLoggedIn) {\n        throw new Error(\"Please login first\");\n      }\n\n      console.log(`${userStore.name} purchased ${cartStore.itemCount} items`);\n      console.log(`Total: ${cartStore.total}`);\n\n      cartStore.clearCart();\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"warning\" title=\"Beware of Circular References\">\n\nIf Store A uses Store B and Store B uses Store A, you'll create a circular reference.\nIn such cases, consider extracting common state into a separate store.\n\n</KawaikoNote>\n\n## SSR Support\n\nchibivue-store supports Server-Side Rendering (SSR).\n\n### store.state Property\n\nThe root store's `state` property allows you to serialize and hydrate all store states.\n\n```ts\n// Store interface\ninterface Store {\n  install: (app: App) => void;\n  state: Ref<Record<string, StateTree>>;  // Holds all store states\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n`state` is created as `ref({})`, and each store's state is saved in `state.value[storeId]`.\nThis enables:\n- SSR: Serialize server-side state with `JSON.stringify(store.state.value)`\n- Client hydration: Restore with `store.state.value = serverState`\n\n### Server-Side: Serializing State\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // Important: Create new instances for each request\n  // This prevents Cross-Request State Pollution\n  const store = createStore();\n  const app = createApp(App);\n  app.use(store);\n\n  const html = await renderToString(app);\n\n  // Serialize store state\n  const storeState = JSON.stringify(store.state.value);\n\n  return { html, storeState };\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"New Instance Per Request\">\n\nNote that `createStore()` and `createApp()` are called inside the `render()` function.\n**You must not create them as singletons at module scope**.\n\n```ts\n// BAD: Creating at module scope is dangerous\nconst store = createStore();  // Shared across all requests!\nconst app = createApp(App);\n\nexport async function render() {\n  // store and app are shared across all requests\n}\n```\n\n</KawaikoNote>\n\n### Embedding in HTML\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      // Embed serialized state from server\n      window.__STORE_STATE__ = ${storeState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### Client-Side: Hydrating State\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nconst store = createStore();\nconst app = createApp(App);\napp.use(store);\n\n// Hydrate with server state\nif (window.__STORE_STATE__) {\n  store.state.value = window.__STORE_STATE__;\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"warning\" title=\"Store Initialization Order\">\n\nStores must be initialized before hydration.\nStores used by components (`useXxxStore()`) are automatically initialized during `app.mount()`.\n\nIf you need to hydrate before mounting, initialize the stores first:\n\n```ts\n// Initialize stores first\nuseCounterStore();\nuseUserStore();\n\n// Then hydrate\nstore.state.value = window.__STORE_STATE__;\n\napp.mount(\"#app\");\n```\n\n</KawaikoNote>\n\n### How state Works\n\nIn the new implementation, `state` is created as `ref({})` and stores state directly:\n\n```ts\n// createStore.ts\nconst state = scope.run(() => ref({}))!;\n```\n\nWhen each store is created, its state is saved to `store.state.value[id]`:\n\n```ts\n// store.ts (inside createSetupStore, createOptionsStore)\nstore.state.value[id] = stateFn ? stateFn() : {};\n```\n\nThis design enables:\n- SSR: Directly serialize `store.state.value` with `JSON.stringify`\n- Hydration: Directly restore with `store.state.value = serverState`\n- Each store's setup/state function uses existing `state.value[id]` if present (for hydration)\n\n<KawaikoNote variant=\"surprise\" title=\"SSR Ready!\">\n\nchibivue-store now supports SSR.\nBy transferring state computed on the server to the client, you can maintain consistent state after hydration.\n\n</KawaikoNote>\n\n## Future Extensions\n\nThe current implementation covers basic functionality, but Pinia also has:\n\n1. **$subscribe**: Subscribe to state changes\n2. **$onAction**: Monitor action execution\n3. **Plugin System**: Extend store functionality\n4. **Devtools Integration**: State visualization and time-travel debugging\n5. **mapState / mapActions**: Helpers for Options API components\n\n<KawaikoNote variant=\"surprise\" title=\"Implementation Complete!\">\n\nWe've completed a Pinia-like store.\nWith about 150 lines of code, we've achieved state management.\nThis should be a good starting point for understanding how Pinia works.\n\n</KawaikoNote>\n\n## Summary\n\nThe chibivue-store implementation consists of:\n\n1. **Root Store Creation**: Install as Vue plugin with `createStore`\n2. **Dependency Injection**: Share store throughout component tree via `provide/inject`\n3. **Two Definition Styles**: Support both Composition API and Options API\n4. **Getters**: Define derived state using `computed`\n5. **Actions**: Methods with access to state and getters\n6. **$patch**: Batch state updates\n7. **$reset**: Reset state to initial values (Options API only)\n8. **Singleton Pattern**: Each store ID creates only one instance\n9. **SSR Support**: Serialize and hydrate state via `store.state`\n\nBy combining Vue's plugin system, provide/inject, and reactivity system, we've achieved global state management.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/010-plugins/030-data-fetch.md",
    "content": "# Data Fetch\n\n## What is a Data Fetch Library?\n\nModern web applications frequently fetch data from servers. In the Vue.js ecosystem, libraries like Pinia Colada and TanStack Query provide this functionality.\n\nIn this chapter, we'll implement basic data fetching functionality similar to Pinia Colada as chibivue-fetch.\n\n### Why Do We Need a Library?\n\nSimple data fetching might seem sufficient with just `fetch` and `ref`:\n\n```ts\n// composables/useUser.ts\nimport { ref, onMounted } from \"chibivue\";\n\nexport function useUser(id: number) {\n  const user = ref(null);\n  const isLoading = ref(true);\n  const error = ref(null);\n\n  onMounted(async () => {\n    try {\n      const response = await fetch(`/api/users/${id}`);\n      user.value = await response.json();\n    } catch (e) {\n      error.value = e;\n    } finally {\n      isLoading.value = false;\n    }\n  });\n\n  return { user, isLoading, error };\n}\n```\n\nHowever, this implementation has the following problems:\n\n1. **No caching**: Same data is fetched multiple times\n2. **SSR is difficult**: Cannot transfer server-fetched data to the client\n3. **Duplicate requests**: Multiple mounts of the same component cause duplicate requests\n4. **Error handling**: Retry and refetch logic becomes complex\n\nData fetching libraries solve these problems and provide a declarative API.\n\n## Package Structure\n\nchibivue-fetch is provided in the `@extensions/chibivue-fetch` package.\n\n```\n@extensions/chibivue-fetch/src/\n├── index.ts           # Exports\n├── queryCache.ts      # QueryCache implementation (cache management)\n├── useQuery.ts        # Data fetching hook\n├── useMutation.ts     # Data mutation hook\n└── types.ts           # Type definitions\n```\n\n## Data State Pattern\n\nSimilar to Pinia Colada, chibivue-fetch represents data state with three states:\n\n```ts\ntype DataStateStatus = \"pending\" | \"error\" | \"success\";\n\ntype DataState<TData, TError> =\n  | { status: \"pending\"; data: undefined; error: null }\n  | { status: \"error\"; data: TData | undefined; error: TError }\n  | { status: \"success\"; data: TData; error: null };\n```\n\nThis state model allows clear tracking of data state.\n\n## QueryCache\n\n`QueryCache` manages caching and state management for SSR.\n\n```ts\n// queryCache.ts\nexport interface QueryCache {\n  install: (app: App) => void;\n  caches: Map<string, UseQueryEntry>;\n  options: Required<QueryCacheOptions>;\n  create: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults | null, ...) => UseQueryEntry;\n  ensure: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults) => UseQueryEntry;\n  fetch: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  refresh: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  invalidate: (entry: UseQueryEntry) => void;\n  invalidateQueries: (key?: EntryKey) => void;\n  remove: (entry: UseQueryEntry) => void;\n  track: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  untrack: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  setQueryData: <TData>(key: EntryKey, data: TData) => void;\n  getQueryData: <TData>(key: EntryKey) => TData | undefined;\n  prefetchQuery: <TData>(key: EntryKey, queryFn: (ctx: QueryContext) => Promise<TData>, options?: Partial<UseQueryOptionsWithDefaults>) => Promise<void>;\n  isStale: (entry: UseQueryEntry) => boolean;\n}\n```\n\n### Key Methods\n\n- `ensure`: Get or create an entry\n- `fetch`: Execute the query (always executes)\n- `refresh`: Refresh the query (only executes if stale or error)\n- `invalidate`: Invalidate an entry (mark as stale)\n- `invalidateQueries`: Invalidate entries matching a key\n- `track` / `untrack`: Track component dependencies\n- `setQueryData` / `getQueryData`: Direct cache data manipulation\n- `prefetchQuery`: Prefetch data and store in cache\n\n### createQueryCache\n\n```ts\nimport { createQueryCache } from \"chibivue-fetch\";\n\nconst queryCache = createQueryCache({\n  staleTime: 5000,       // Default stale time (5 seconds)\n  gcTime: 300000,        // Default GC time (5 minutes)\n});\n\napp.use(queryCache);\n```\n\n## useQuery\n\n`useQuery` is a composable for data fetching.\n\n```ts\n// useQuery.ts\nexport interface UseQueryOptions<TData = unknown, TError = Error> {\n  key: EntryKey | EntryKeyFn;\n  query: (context: QueryContext) => Promise<TData>;\n  staleTime?: number;\n  gcTime?: number;\n  refetchOnMount?: boolean | \"always\";\n  initialData?: TData | (() => TData);\n  enabled?: boolean | Ref<boolean> | ComputedRef<boolean>;\n  retry?: number | boolean;\n  retryDelay?: number;\n  meta?: QueryMeta;\n}\n\nexport interface UseQueryReturn<TData = unknown, TError = Error> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  refresh: () => Promise<DataState<TData, TError>>;\n  refetch: () => Promise<DataState<TData, TError>>;\n}\n```\n\n### Options\n\n- `key`: Unique key for the query (used as cache key)\n- `query`: Async function to fetch data (receives `{ signal }`)\n- `staleTime`: Time until data becomes \"stale\"\n- `gcTime`: Time to keep unused cache (garbage collection)\n- `enabled`: Whether to enable the query\n- `retry`: Number of retries on error\n- `initialData`: Initial data\n\n### States\n\n- `status`: Current status (`\"pending\"` | `\"error\"` | `\"success\"`)\n- `asyncStatus`: Async status (`\"idle\"` | `\"loading\"`)\n- `isPending`: No initial data yet\n- `isLoading`: Initial fetching (`isPending` and `asyncStatus === \"loading\"`)\n- `isSuccess`: Fetch succeeded\n- `isError`: Fetch failed\n\n### Difference between refresh and refetch\n\n- `refresh()`: Only fetches if stale or error\n- `refetch()`: Always fetches (invalidates cache first)\n\n### Usage Example\n\n```ts\nimport { useQuery } from \"chibivue-fetch\";\n\nconst { data, isLoading, error, refresh } = useQuery({\n  key: [\"user\", userId],\n  query: ({ signal }) => fetch(`/api/users/${userId}`, { signal }).then((res) => res.json()),\n  staleTime: 60000, // Use cache for 1 minute\n});\n```\n\n## useMutation\n\n`useMutation` is a composable for data mutations (POST, PUT, DELETE, etc.).\n\n```ts\n// useMutation.ts\nexport interface UseMutationOptions<TData, TError, TVariables, TContext> {\n  mutation: (variables: TVariables) => Promise<TData>;\n  onMutate?: (variables: TVariables) => TContext | Promise<TContext>;\n  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n}\n\nexport interface UseMutationReturn<TData, TError, TVariables> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  variables: ShallowRef<TVariables | undefined>;\n  mutate: (variables: TVariables) => void;\n  mutateAsync: (variables: TVariables) => Promise<TData>;\n  reset: () => void;\n}\n```\n\n### Lifecycle Callbacks\n\n- `onMutate`: Called before mutation executes (returns context)\n- `onSuccess`: Called on success\n- `onError`: Called on error\n- `onSettled`: Called at the end regardless of success or error\n\n### Usage Example\n\n```ts\nimport { useMutation } from \"chibivue-fetch\";\n\nconst { mutate, isLoading, isSuccess } = useMutation({\n  mutation: (newUser) => fetch(\"/api/users\", {\n    method: \"POST\",\n    body: JSON.stringify(newUser),\n  }).then((res) => res.json()),\n  onSuccess: (data) => {\n    console.log(\"User created:\", data);\n    // Invalidate cache to trigger refetch\n    queryCache.invalidateQueries([\"users\"]);\n  },\n});\n\n// Usage\nmutate({ name: \"John\", email: \"john@example.com\" });\n```\n\n## How Caching Works\n\n### Entry Key\n\n`key` is used as the cache key. Array format allows hierarchical keys:\n\n```ts\n// Simple key\nkey: [\"users\"]\n\n// Hierarchical key\nkey: [\"users\", userId]\n\n// Key with object\nkey: [\"users\", { status: \"active\", page: 1 }]\n```\n\nQueries with the same `key` share the cache. Keys are serialized as sorted JSON, so object property order doesn't matter.\n\n### Stale Time and GC Time\n\n```\n       ← staleTime →|← refetch window →|← gcTime →|\n  fetch             stale               inactive   gc\n    |-----------------|----------------------|-----|\n    data arrives      data is stale         data removed\n```\n\n- **staleTime**: Period during which data is \"fresh\". Calling `refresh()` during this time won't fetch\n- **gcTime**: Period to keep unused cache. After a component unmounts, cache is deleted after this period\n\n```ts\n// No refetch for 1 minute, keep cache for 5 minutes\nuseQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  staleTime: 60 * 1000,  // 1 minute\n  gcTime: 5 * 60 * 1000, // 5 minutes\n});\n```\n\n### Dependency Tracking\n\nSimilar to Pinia Colada, chibivue-fetch tracks which components are using each query entry:\n\n```ts\n// Track when component mounts\nonMounted(() => {\n  queryCache.track(entry, currentInstance);\n});\n\n// Untrack when component unmounts\nonUnmounted(() => {\n  queryCache.untrack(entry, currentInstance);\n});\n```\n\nWhen there are no more dependencies, the cache is garbage collected after `gcTime`.\n\n## SSR Support\n\nchibivue-fetch supports Server-Side Rendering (SSR).\n\n### Server-Side: Serializing State\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createQueryCache, serializeQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // Create new instances for each request\n  const queryCache = createQueryCache();\n  const app = createApp(App);\n  app.use(queryCache);\n\n  // Prefetch data on server\n  await queryCache.prefetchQuery(\n    [\"users\"],\n    ({ signal }) => fetch(\"http://api/users\", { signal }).then((r) => r.json()),\n  );\n\n  const html = await renderToString(app);\n\n  // Serialize cache state\n  const queryState = JSON.stringify(serializeQueryCache(queryCache));\n\n  return { html, queryState };\n}\n```\n\n### Serialization Format\n\nSimilar to Pinia Colada, we use relative timestamps for serialization:\n\n```ts\n// UseQueryEntryNodeSerialized = [data, error, when (relative), meta]\n{\n  '[\"users\"]': [\n    [{ id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }], // data\n    null,                                                // error\n    0,                                                   // when (relative: now - fetchTime)\n    undefined                                            // meta\n  ]\n}\n```\n\nRelative timestamps handle time differences between server and client.\n\n### Embedding in HTML\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.__QUERY_STATE__ = ${queryState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### Client-Side: Hydrating State\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createQueryCache, hydrateQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nconst queryCache = createQueryCache();\nconst app = createApp(App);\napp.use(queryCache);\n\n// Hydrate with server state\nif (window.__QUERY_STATE__) {\n  hydrateQueryCache(queryCache, window.__QUERY_STATE__);\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\nIn SSR, similar to Store, you must be aware of **Cross-Request State Pollution**.\nCall `createQueryCache()` inside the `render()` function and create new instances for each request.\n\n</KawaikoNote>\n\n## Practical Examples\n\n### Reactive Query Key\n\n```ts\nimport { ref, computed } from \"chibivue\";\nimport { useQuery } from \"chibivue-fetch\";\n\nconst page = ref(1);\nconst filters = ref({ status: \"active\" });\n\nconst { data, isLoading } = useQuery({\n  // Function format for dynamic keys\n  key: () => [\"users\", { page: page.value, ...filters.value }],\n  query: ({ signal }) => fetchUsers(page.value, filters.value, signal),\n});\n\n// Automatically refetches when page or filters change\nfunction nextPage() {\n  page.value++;\n}\n```\n\n### Conditional Query\n\n```ts\nconst userId = ref<number | null>(null);\n\nconst { data: user } = useQuery({\n  key: () => [\"user\", userId.value],\n  query: ({ signal }) => fetchUser(userId.value!, signal),\n  // Query won't execute while userId is null\n  enabled: computed(() => userId.value !== null),\n});\n```\n\n### Cache Update After Mutation\n\n```ts\nconst queryCache = getActiveQueryCache();\n\nconst { mutate: createUser } = useMutation({\n  mutation: (newUser) => api.createUser(newUser),\n  onSuccess: (createdUser) => {\n    // Method 1: Invalidate cache and refetch\n    queryCache.invalidateQueries([\"users\"]);\n\n    // Method 2: Update cache directly (optimistic update)\n    const currentUsers = queryCache.getQueryData<User[]>([\"users\"]);\n    if (currentUsers) {\n      queryCache.setQueryData([\"users\"], [...currentUsers, createdUser]);\n    }\n  },\n});\n```\n\n### Error Handling and Retry\n\n```ts\nconst { data, error, refresh } = useQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  retry: 3,        // Retry up to 3 times\n  retryDelay: 1000, // Retry after 1 second\n});\n\n// In component\nif (error.value) {\n  // Show error and retry button\n}\n```\n\n### Cancellation with AbortController\n\n```ts\nconst { data } = useQuery({\n  key: [\"users\"],\n  query: async ({ signal }) => {\n    const response = await fetch(\"/api/users\", { signal });\n    if (!response.ok) throw new Error(\"Failed to fetch\");\n    return response.json();\n  },\n});\n```\n\nWhen a query is cancelled (e.g., when a new request starts), the `signal` is aborted.\n\n## Summary\n\nchibivue-fetch implementation consists of the following elements:\n\n1. **QueryCache**: Centralized cache management and dependency tracking\n2. **Data State Pattern**: Three-state model of `pending | error | success`\n3. **useQuery**: Declarative data fetching API\n4. **useMutation**: Data mutation management with lifecycle callbacks\n5. **Cache Strategy**: Flexible control with staleTime / gcTime\n6. **SSR Support**: State transfer via `serializeQueryCache()` / `hydrateQueryCache()`\n7. **Reactive Keys**: Dynamic query key support\n8. **Error Handling**: Automatic retry and state management\n9. **AbortController**: Request cancellation support\n\nBy implementing the core features of Pinia Colada minimally, you can understand how data fetching works.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/010-plugins/040-language-tools.md",
    "content": "# Language Tools\n\n## What are Language Tools?\n\nLanguage tools provide IDE support for `.vue` Single File Components (SFCs). They enable features like:\n\n- Syntax highlighting\n- Auto-completion\n- Type checking\n- Go to definition\n- Error diagnostics\n\nIn the Vue.js ecosystem, [vuejs/language-tools](https://github.com/vuejs/language-tools) provides this functionality, built on [Volar.js](https://volarjs.dev/) as its foundation. In this chapter, we'll implement minimal language tools for chibivue using Volar.js.\n\n## Why Do We Need Language Tools?\n\nTypeScript's language service can only understand `.ts` and `.tsx` files. However, `.vue` files contain multiple languages mixed together:\n\n```vue\n<template>\n  <div>{{ message }}</div>  <!-- HTML + expressions -->\n</template>\n\n<script setup lang=\"ts\">\nconst message = ref('Hello')  // TypeScript\n</script>\n\n<style scoped>\ndiv { color: red; }  /* CSS */\n</style>\n```\n\nThe role of Language Tools is to **transform** this composite file into a format that TypeScript's language service can understand. This transformation enables all TypeScript features (type checking, auto-completion, refactoring, etc.) to work within `.vue` files.\n\n## Architecture Overview\n\nLanguage tools consist of three main packages:\n\n```txt\n@extensions/\n├── chibivue-language-core/     # Core language processing\n│   ├── parseSfc.ts             # SFC parser\n│   ├── virtualCode.ts          # Virtual code generation\n│   ├── languagePlugin.ts       # Volar.js plugin\n│   └── types.ts                # Type definitions\n├── chibivue-language-server/   # LSP server\n│   └── server.ts               # Language Server Protocol server\n└── vscode-chibivue/            # VSCode extension\n    ├── extension.ts            # Extension entry point\n    ├── syntaxes/               # TextMate grammar\n    └── language-configuration.json\n```\n\n### Data Flow\n\nWhen you edit a `.vue` file in your editor, data flows through the following pipeline:\n\n```txt\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                              VSCode                                         │\n│  ┌─────────────┐                                                            │\n│  │  App.vue    │  User edits .vue file                                      │\n│  └──────┬──────┘                                                            │\n│         │                                                                   │\n│         ▼                                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  vscode-chibivue    │  VSCode extension detects file changes             │\n│  │  (Language Client)  │                                                    │\n│  └──────────┬──────────┘                                                    │\n└─────────────┼───────────────────────────────────────────────────────────────┘\n              │ LSP (Language Server Protocol)\n              ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│  chibivue-language-server                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  Language Server    │  Receives LSP requests                             │\n│  └──────────┬──────────┘                                                    │\n│             │                                                               │\n│             ▼                                                               │\n│  ┌─────────────────────┐    ┌─────────────────────┐                         │\n│  │  chibivue-language  │───▶│  Virtual Code       │                         │\n│  │  -core (Plugin)     │    │  (.vue → .ts)       │                         │\n│  └─────────────────────┘    └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  TypeScript         │                         │\n│                             │  Language Service   │  Type checking, etc.    │\n│                             └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  Code Mappings      │  Map results back       │\n│                             └─────────────────────┘                         │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Core Concepts\n\n### Virtual Code\n\nThe core concept in Language Tools is **virtual code**. By transforming `.vue` files into TypeScript, we can leverage all features of the TypeScript language service.\n\n#### Transformation Example\n\n```vue\n<!-- Original .vue file -->\n<template>\n  <div>{{ message }}</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n</script>\n```\n\nThis is transformed into virtual TypeScript:\n\n```ts\n// Virtual TypeScript code\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n\n// Code for type-checking template expressions\n// Not actually executed, but allows TypeScript to verify expression types\ndeclare const __VLS_template: () => void;\n(() => {\n  // Corresponds to {{ message }} in template\n  // TypeScript verifies that message exists and has correct type\n  const __VLS_expr0 = (message);\n})();\n```\n\nThis transformation enables:\n- Verification that `message` has type `Ref<string>`\n- Error reporting if `message` is undefined\n- Type information display when hovering over `message`\n\n### Code Mappings\n\n**Code mappings** link positions in virtual code back to positions in the original `.vue` file.\n\n```txt\nOriginal .vue file                    Virtual TypeScript\n─────────────────────────────────────────────────────────────\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'  ←──→  import { ref } from 'chibivue'\n                                      ↑\nconst message = ref('Hello')    ←──→  const message = ref('Hello')\n</script>                             ↑\n                                      │\n<template>                            │\n  <div>{{ message }}</div>      ←─────┼──→  const __VLS_expr0 = (message);\n</template>                           │\n                                      ↓\n                                Mappings link positions together\n```\n\nWith mappings:\n- Errors in virtual code → Display at correct position in original `.vue` file\n- \"Go to Definition\" → Transform virtual code position to original file position\n\n## Implementation\n\n### Type Definitions\n\nFirst, we define types to represent the structure of an SFC.\n\n```ts\n// types.ts\n\n/**\n * Represents each block (template, script, style) in an SFC\n */\nexport interface SfcBlock {\n  /** Block type (\"template\", \"script\", \"style\", etc.) */\n  type: string;\n\n  /** Block content (inner content without tags) */\n  content: string;\n\n  /** Position information (used for error display and mappings) */\n  loc: {\n    start: { line: number; column: number; offset: number };\n    end: { line: number; column: number; offset: number };\n  };\n\n  /** Block attributes (e.g., lang=\"ts\", scoped) */\n  attrs: Record<string, string | true>;\n\n  /** Language specification (shortcut for attrs.lang) */\n  lang?: string;\n}\n\n/**\n * Represents the entire parsed SFC\n */\nexport interface SfcDescriptor {\n  /** <template> block */\n  template: SfcBlock | null;\n\n  /** <script> (without setup) block */\n  script: SfcBlock | null;\n\n  /** <script setup> block */\n  scriptSetup: SfcBlock | null;\n\n  /** <style> blocks (can have multiple) */\n  styles: SfcBlock[];\n\n  /** Custom blocks (e.g., <docs>) */\n  customBlocks: SfcBlock[];\n}\n```\n\n### SFC Parser\n\nParse `.vue` files to generate `SfcDescriptor`.\n\n::: tip\nIn actual implementation, you can use the `parse` function from chibivue's `@chibivue/compiler-sfc` package. Here we show a simplified parser for educational purposes.\n:::\n\n```ts\n// parseSfc.ts\nimport type { SfcBlock, SfcDescriptor } from './types';\n\n/**\n * Parse .vue file content and return SfcDescriptor\n *\n * @param content - Content of .vue file\n * @param fileName - File name (for error messages)\n */\nexport function parseSfc(content: string, fileName: string): SfcDescriptor {\n  const descriptor: SfcDescriptor = {\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n    customBlocks: [],\n  };\n\n  // Regex to match top-level blocks\n  // Detects <tagName attrs>content</tagName> format\n  const blockRegex = /<(\\w+)([^>]*)>([\\s\\S]*?)<\\/\\1>/g;\n  let match: RegExpExecArray | null;\n\n  while ((match = blockRegex.exec(content)) !== null) {\n    const [fullMatch, tagName, attrsString, blockContent] = match;\n\n    // Calculate block start position\n    const startOffset = match.index + `<${tagName}${attrsString}>`.length;\n    const startPos = offsetToPosition(content, startOffset);\n\n    // Calculate block end position\n    const endOffset = startOffset + blockContent.length;\n    const endPos = offsetToPosition(content, endOffset);\n\n    // Parse attributes (e.g., 'lang=\"ts\" scoped' → { lang: \"ts\", scoped: true })\n    const attrs = parseAttrs(attrsString);\n\n    const block: SfcBlock = {\n      type: tagName,\n      content: blockContent,\n      loc: {\n        start: { ...startPos, offset: startOffset },\n        end: { ...endPos, offset: endOffset },\n      },\n      attrs,\n      lang: typeof attrs.lang === 'string' ? attrs.lang : undefined,\n    };\n\n    // Categorize by block type\n    switch (tagName) {\n      case 'template':\n        descriptor.template = block;\n        break;\n      case 'script':\n        // Categorize by presence of setup attribute\n        if ('setup' in attrs) {\n          descriptor.scriptSetup = block;\n        } else {\n          descriptor.script = block;\n        }\n        break;\n      case 'style':\n        descriptor.styles.push(block);\n        break;\n      default:\n        descriptor.customBlocks.push(block);\n    }\n  }\n\n  return descriptor;\n}\n\n/**\n * Calculate line/column numbers from offset (character position)\n */\nfunction offsetToPosition(\n  content: string,\n  offset: number\n): { line: number; column: number } {\n  const lines = content.slice(0, offset).split('\\n');\n  return {\n    line: lines.length,\n    column: lines[lines.length - 1].length + 1,\n  };\n}\n\n/**\n * Parse attribute string to object\n * Example: ' lang=\"ts\" scoped' → { lang: \"ts\", scoped: true }\n */\nfunction parseAttrs(attrsString: string): Record<string, string | true> {\n  const attrs: Record<string, string | true> = {};\n  const attrRegex = /(\\w+)(?:=\"([^\"]*)\"|='([^']*)')?/g;\n  let attrMatch: RegExpExecArray | null;\n\n  while ((attrMatch = attrRegex.exec(attrsString)) !== null) {\n    const [, name, doubleQuoted, singleQuoted] = attrMatch;\n    attrs[name] = doubleQuoted ?? singleQuoted ?? true;\n  }\n\n  return attrs;\n}\n```\n\n### Virtual Code Generation\n\nImplement the Volar.js `VirtualCode` interface. This is the heart of Language Tools.\n\n```ts\n// virtualCode.ts\nimport type {\n  CodeMapping,\n  VirtualCode,\n} from '@volar/language-core';\nimport type * as ts from 'typescript';\nimport { parseSfc } from './parseSfc';\nimport type { SfcDescriptor } from './types';\n\n/**\n * Code segment: A piece of generated code with its mapping info\n */\ntype CodeSegment = [\n  code: string,                           // Code to generate\n  sourceOffsetStart?: number,             // Start position in source file\n  sourceOffsetEnd?: number,               // End position in source file\n  features?: { verification?: boolean },  // Mapping feature settings\n];\n\n/**\n * Class that transforms .vue files into virtual TypeScript code\n */\nexport class ChibivueVirtualCode implements VirtualCode {\n  id = 'root';\n  languageId = 'vue';\n  snapshot: ts.IScriptSnapshot;\n  mappings: CodeMapping[] = [];\n  embeddedCodes: VirtualCode[] = [];\n\n  private fileName: string;\n  private sfc: SfcDescriptor;\n\n  constructor(fileName: string, snapshot: ts.IScriptSnapshot) {\n    this.fileName = fileName;\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * Called when file is updated\n   */\n  update(snapshot: ts.IScriptSnapshot): void {\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, this.fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * Main process to generate virtual code\n   */\n  private generateVirtualCode(sourceContent: string): void {\n    const segments: CodeSegment[] = [];\n\n    // 1. Generate code from script/scriptSetup\n    this.generateScriptCode(segments);\n\n    // 2. Generate template type-checking code\n    this.generateTemplateCode(segments);\n\n    // 3. Build final code and mappings from segments\n    const { code, mappings } = this.buildCode(segments, sourceContent);\n\n    // 4. Register as embedded code (TypeScript)\n    this.embeddedCodes = [\n      {\n        id: 'ts',\n        languageId: 'typescript',\n        snapshot: createScriptSnapshot(code),\n        mappings,\n        embeddedCodes: [],\n      },\n    ];\n  }\n\n  /**\n   * Generate TypeScript code from script/scriptSetup blocks\n   */\n  private generateScriptCode(segments: CodeSegment[]): void {\n    const { script, scriptSetup } = this.sfc;\n\n    if (scriptSetup) {\n      // Output <script setup> content as-is\n      // Add mapping info (link to source file position)\n      segments.push([\n        scriptSetup.content,\n        scriptSetup.loc.start.offset,\n        scriptSetup.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    } else if (script) {\n      // Output <script> content as-is\n      segments.push([\n        script.content,\n        script.loc.start.offset,\n        script.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    }\n  }\n\n  /**\n   * Generate code for type-checking template expressions\n   */\n  private generateTemplateCode(segments: CodeSegment[]): void {\n    const { template } = this.sfc;\n    if (!template) return;\n\n    // Add template type-checking code\n    segments.push(['\\n// Template type-checking\\n']);\n    segments.push(['declare const __VLS_template: () => void;\\n']);\n\n    // Detect mustache expressions {{ expr }}\n    const mustacheRegex = /\\{\\{\\s*([\\s\\S]*?)\\s*\\}\\}/g;\n    let match: RegExpExecArray | null;\n    let exprIndex = 0;\n\n    while ((match = mustacheRegex.exec(template.content)) !== null) {\n      const expr = match[1];\n      // Calculate expression position in source file\n      const exprStartInTemplate = match.index + match[0].indexOf(expr);\n      const sourceStart = template.loc.start.offset + exprStartInTemplate;\n      const sourceEnd = sourceStart + expr.length;\n\n      // Generate code to verify expression\n      // (() => { const __VLS_expr0 = (message); })();\n      segments.push([`(() => {\\n  const __VLS_expr${exprIndex} = (`]);\n      segments.push([\n        expr,\n        sourceStart,\n        sourceEnd,\n        { verification: true },\n      ]);\n      segments.push([');\\n})();\\n']);\n\n      exprIndex++;\n    }\n  }\n\n  /**\n   * Build final code and mappings from segments\n   */\n  private buildCode(\n    segments: CodeSegment[],\n    sourceContent: string\n  ): { code: string; mappings: CodeMapping[] } {\n    let code = '';\n    const mappings: CodeMapping[] = [];\n\n    for (const segment of segments) {\n      const [text, sourceStart, sourceEnd, features] = segment;\n\n      if (sourceStart !== undefined && sourceEnd !== undefined) {\n        // Record mapping info when present\n        mappings.push({\n          sourceOffsets: [sourceStart],\n          generatedOffsets: [code.length],\n          lengths: [sourceEnd - sourceStart],\n          data: {\n            verification: features?.verification ?? false,\n            completion: true,\n            semantic: true,\n            navigation: true,\n            structure: true,\n            format: false,\n          },\n        });\n      }\n\n      code += text;\n    }\n\n    return { code, mappings };\n  }\n}\n\n/**\n * Create TypeScript script snapshot\n */\nfunction createScriptSnapshot(content: string): ts.IScriptSnapshot {\n  return {\n    getText: (start, end) => content.slice(start, end),\n    getLength: () => content.length,\n    getChangeRange: () => undefined,\n  };\n}\n```\n\n### Language Plugin\n\nImplement a plugin that tells Volar.js how to handle `.vue` files.\n\n```ts\n// languagePlugin.ts\nimport type { LanguagePlugin } from '@volar/language-core';\nimport { ChibivueVirtualCode } from './virtualCode';\n\n/**\n * Create language plugin for Volar.js\n *\n * This plugin is responsible for:\n * 1. Identifying .vue files\n * 2. Generating virtual code from .vue files\n * 3. Providing virtual code to TypeScript language service\n */\nexport function createChibivueLanguagePlugin(): LanguagePlugin<\n  string,\n  ChibivueVirtualCode\n> {\n  return {\n    /**\n     * Determine language ID from file extension\n     * Returns \"vue\" for .vue files\n     */\n    getLanguageId(scriptId: string): string | undefined {\n      if (scriptId.endsWith('.vue')) {\n        return 'vue';\n      }\n      return undefined;\n    },\n\n    /**\n     * Create new virtual code\n     * Called when file is first opened\n     */\n    createVirtualCode(scriptId, languageId, snapshot) {\n      if (languageId === 'vue') {\n        return new ChibivueVirtualCode(scriptId, snapshot);\n      }\n      return undefined;\n    },\n\n    /**\n     * Update existing virtual code\n     * Called when file is edited\n     */\n    updateVirtualCode(_scriptId, virtualCode, snapshot) {\n      virtualCode.update(snapshot);\n      return virtualCode;\n    },\n\n    /**\n     * TypeScript-specific settings\n     */\n    typescript: {\n      /**\n       * Settings to make TypeScript recognize .vue files\n       *\n       * - extension: Target file extension\n       * - isMixedContent: Indicates multiple languages are contained\n       * - scriptKind: TypeScript's ScriptKind\n       *   - 7 = Deferred (lazy evaluation, uses virtual code)\n       */\n      extraFileExtensions: [\n        { extension: 'vue', isMixedContent: true, scriptKind: 7 },\n      ],\n\n      /**\n       * Get script to pass to TypeScript from virtual code\n       *\n       * @returns\n       *   - code: Embedded TypeScript code\n       *   - extension: \".ts\" (treat as TypeScript)\n       *   - scriptKind: 3 = TS (regular TypeScript)\n       */\n      getServiceScript(rootVirtualCode) {\n        for (const code of rootVirtualCode.embeddedCodes) {\n          if (code.id === 'ts') {\n            return {\n              code,\n              extension: '.ts',\n              scriptKind: 3, // ts.ScriptKind.TS\n            };\n          }\n        }\n        return undefined;\n      },\n    },\n  };\n}\n```\n\n### Language Server\n\nThe LSP (Language Server Protocol) server bridges the editor and language features.\n\n```ts\n// server.ts\nimport {\n  createConnection,\n  createServer,\n  createSimpleProjectProviderFactory,\n  loadTsdkByPath,\n} from '@volar/language-server/node';\nimport { create as createTypeScriptServices } from 'volar-service-typescript';\nimport { createChibivueLanguagePlugin } from '@chibivue/language-core';\n\n/**\n * About LSP (Language Server Protocol)\n *\n * LSP is a protocol for separating editors from language features.\n *\n * ┌──────────┐                        ┌──────────────────┐\n * │  VSCode  │ ◄───── LSP comm ─────► │  Language Server │\n * │  Neovim  │    (JSON-RPC over      │  (this file)     │\n * │  Emacs   │     stdio/IPC)         │                  │\n * └──────────┘                        └──────────────────┘\n *\n * Main LSP requests:\n * - textDocument/completion: Get auto-completion candidates\n * - textDocument/hover: Get hover information\n * - textDocument/definition: Go to definition\n * - textDocument/references: Find references\n * - textDocument/rename: Rename symbol\n * - textDocument/diagnostics: Error diagnostics\n */\n\n// Create LSP connection (communicate via stdin/stdout or IPC)\nconst connection = createConnection();\n\n// Create Volar language server\nconst server = createServer(connection);\n\n// Start listening on connection\nconnection.listen();\n\n/**\n * Handler for initialization request\n * Called when client (editor) connects\n */\nconnection.onInitialize((params) => {\n  // Get TypeScript SDK path (passed from client)\n  const tsdk = params.initializationOptions?.typescript?.tsdk;\n\n  // Load TypeScript module\n  const ts = tsdk\n    ? loadTsdkByPath(tsdk, params.locale)\n    : require('typescript');\n\n  // Create chibivue language plugin\n  const chibivuePlugin = createChibivueLanguagePlugin();\n\n  // Initialize server and register capabilities\n  return server.initialize(\n    params,\n    // Project management settings (tsconfig.json detection, etc.)\n    createSimpleProjectProviderFactory(),\n    {\n      /**\n       * Return language plugins\n       * Responsible for virtual code generation from .vue files\n       */\n      getLanguagePlugins() {\n        return [chibivuePlugin];\n      },\n\n      /**\n       * Return service plugins\n       * Provide TypeScript language features (completion, diagnostics, etc.)\n       */\n      getServicePlugins() {\n        return [...createTypeScriptServices(ts)];\n      },\n    }\n  );\n});\n\n/**\n * Handler for initialization complete\n */\nconnection.onInitialized(() => {\n  // Additional setup as needed\n});\n```\n\n### VSCode Extension\n\nImplement the extension that connects VSCode to the language server.\n\n```ts\n// extension.ts\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport {\n  LanguageClient,\n  LanguageClientOptions,\n  ServerOptions,\n  TransportKind,\n} from 'vscode-languageclient/node';\n\nlet client: LanguageClient | undefined;\n\n/**\n * Extension activation\n * Called automatically when .vue file is opened\n */\nexport async function activate(context: vscode.ExtensionContext) {\n  // Resolve language server path\n  const serverPath = context.asAbsolutePath(\n    path.join('dist', 'server.js')\n  );\n\n  // Server launch options\n  const serverOptions: ServerOptions = {\n    run: {\n      module: serverPath,\n      transport: TransportKind.ipc, // Communicate via IPC\n    },\n    debug: {\n      module: serverPath,\n      transport: TransportKind.ipc,\n      options: { execArgv: ['--nolazy', '--inspect=6009'] },\n    },\n  };\n\n  // Client options\n  const clientOptions: LanguageClientOptions = {\n    // Which files to process\n    documentSelector: [{ scheme: 'file', language: 'vue' }],\n\n    // Options to pass to server on initialization\n    initializationOptions: {\n      typescript: {\n        // Use VSCode's built-in TypeScript SDK\n        tsdk: path.join(\n          vscode.env.appRoot,\n          'extensions/node_modules/typescript/lib'\n        ),\n      },\n    },\n  };\n\n  // Create Language Client\n  client = new LanguageClient(\n    'chibivue',                    // Client ID\n    'Chibivue Language Server',   // Display name\n    serverOptions,\n    clientOptions\n  );\n\n  // Start language server\n  await client.start();\n\n  // Cleanup on extension deactivation\n  context.subscriptions.push({\n    dispose: () => client?.stop(),\n  });\n}\n\n/**\n * Extension deactivation\n */\nexport function deactivate(): Thenable<void> | undefined {\n  return client?.stop();\n}\n```\n\n### Syntax Highlighting (TextMate Grammar)\n\nSyntax highlighting is defined using TextMate grammar. This uses VSCode's built-in functionality and doesn't involve the language server.\n\n```json\n// syntaxes/vue.tmLanguage.json\n{\n  \"name\": \"Vue\",\n  \"scopeName\": \"source.vue\",\n  \"patterns\": [\n    { \"include\": \"#template\" },\n    { \"include\": \"#script\" },\n    { \"include\": \"#style\" }\n  ],\n  \"repository\": {\n    \"template\": {\n      \"begin\": \"(<)(template)\",\n      \"end\": \"(</)(template)(>)\",\n      \"patterns\": [{ \"include\": \"text.html.basic\" }]\n    },\n    \"script\": {\n      \"begin\": \"(<)(script)\",\n      \"end\": \"(</)(script)(>)\",\n      \"patterns\": [{ \"include\": \"source.ts\" }]\n    },\n    \"style\": {\n      \"begin\": \"(<)(style)\",\n      \"end\": \"(</)(style)(>)\",\n      \"patterns\": [{ \"include\": \"source.css\" }]\n    }\n  }\n}\n```\n\n## Supported Features\n\n| Feature             | Status    | Description                              |\n| ------------------- | --------- | ---------------------------------------- |\n| Syntax Highlighting | Supported | Color coding via TextMate grammar        |\n| Auto-completion     | Supported | Variable, function, property completion  |\n| Type Checking       | Supported | Type error detection via TypeScript      |\n| Go to Definition    | Supported | Jump to variable/function definitions    |\n| Error Diagnostics   | Supported | Display syntax and type errors           |\n| Rename Symbol       | Supported | Bulk rename variables, etc.              |\n| Hover Information   | Supported | Display type info at cursor position     |\n\n## Summary\n\nLanguage Tools enable all TypeScript features in SFCs by transforming `.vue` files into virtual TypeScript code.\n\n**Key Components:**\n\n1. **SFC Parser** - Decompose `.vue` files into template, script, and style blocks\n2. **Virtual Code Generation** - Transform SFC to TypeScript with code mappings\n3. **Language Plugin** - Implement Volar.js interface to provide virtual code\n4. **Language Server** - Communicate with editors via LSP\n5. **VSCode Extension** - Connect VSCode to the language server\n\nThis implementation is minimal for educational purposes. Production implementations like [vuejs/language-tools](https://github.com/vuejs/language-tools) add many advanced features:\n\n- Type checking for template directives (`v-if`, `v-for`, etc.)\n- Component props type validation\n- `<style scoped>` selector completion\n- HTML completion in `<template>`\n- Macro support (`defineProps`, `defineEmits`)\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/020-ssr/010-create-ssr-app.md",
    "content": "# Server Side Rendering (SSR)\n\n## What is SSR\n\nServer Side Rendering (SSR) is a technique that renders Vue.js applications to HTML strings on the server and sends them to the client. This provides the following benefits:\n\n1. **Improved SEO**: Search engine crawlers can obtain complete content\n2. **Faster initial display**: Browsers can display HTML without waiting for JavaScript execution\n3. **Better performance**: Especially effective on slow devices or network environments\n\n## Package Structure\n\nThe SSR implementation of chibivue is provided in the `@chibivue/server-renderer` package.\n\n```\npackages/server-renderer/src/\n├── index.ts\n├── renderToString.ts      # Main entry point\n├── render.ts              # VNode rendering\n└── helpers/\n    ├── ssrRenderAttrs.ts  # Attribute rendering\n    └── ssrUtils.ts        # Utility functions\n```\n\n## Type Definitions\n\n### SSRBuffer\n\nIn SSR, we use a data structure called `SSRBuffer` to efficiently build rendering results.\n\n```ts\n// packages/server-renderer/src/render.ts\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\n```\n\nThe buffer can contain:\n- **Strings**: Parts of HTML\n- **Nested buffers**: Results from child components\n- **Promises**: Results from async components\n\n### SSRContext\n\nHolds context information during SSR.\n\n```ts\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  __watcherHandles?: (() => void)[];\n};\n```\n\n## renderToString Implementation\n\n### Main Entry Point\n\n```ts\n// packages/server-renderer/src/renderToString.ts\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // When VNode is passed directly, wrap it in a wrapper component\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // For App instance\n  const app = input;\n  const vnode = createVNode(app._component, app._props);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  // Cleanup watchers\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n```\n\n### Buffer Unrolling\n\nRecursively unrolls nested buffers and Promises.\n\n```ts\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  // Process synchronously if there are no async elements\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    // Wait for Promise resolution\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    // Recursively process nested buffers\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n```\n\n## createBuffer Implementation\n\nA factory function for efficiently building buffers.\n\n```ts\n// packages/server-renderer/src/render.ts\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        // Optimize by concatenating consecutive strings\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      // Set flag if there are Promises or async buffers\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n```\n\nKey points:\n1. Consecutive strings are automatically concatenated (memory efficiency)\n2. `appendable` flag tracks whether concatenation is possible\n3. `hasAsync` flag is set if there are async elements\n\n## Component Rendering\n\n### renderComponentVNode\n\n```ts\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  // Create component instance\n  const instance = (vnode.component = createComponentInstance(\n    vnode,\n    parentComponent,\n    null,\n  ));\n\n  // Execute setup\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  // Return Promise for async setup\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() =>\n      renderComponentSubTree(instance),\n    );\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n```\n\n### renderComponentSubTree\n\n```ts\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    // Functional component\n    const root = comp(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    // Component with render function\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n```\n\n## VNode Rendering\n\n### renderVNode\n\nRenders according to each VNode type.\n\n```ts\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  // SSR support for directives\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(\n        children\n          ? `<!--${escapeHtmlComment(children as string)}-->`\n          : `<!---->`,\n      );\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      } else if (shapeFlag & ShapeFlags.TELEPORT) {\n        renderTeleportVNode(push, vnode, parentComponent);\n      }\n  }\n}\n```\n\n### renderElementVNode\n\nRenders HTML elements to strings.\n\n```ts\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  // Render attributes\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  // Void tags have no closing tag\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      // Handle special properties\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n```\n\n### renderVNodeChildren\n\nRenders child elements in order.\n\n```ts\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n```\n\n### renderTeleportVNode\n\nSSR support for Teleport components.\n\n```ts\nfunction renderTeleportVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const target = vnode.props && vnode.props.to;\n  const disabled = vnode.props && vnode.props.disabled;\n\n  if (!target) {\n    if (!disabled) {\n      console.warn(`Teleport is missing target prop.`);\n    }\n    return;\n  }\n\n  if (!isString(target)) {\n    console.warn(`Teleport target must be a query selector string.`);\n    return;\n  }\n\n  // Render inline if disabled\n  if (disabled) {\n    renderVNodeChildren(push, vnode.children as VNodeArrayChildren, parentComponent);\n  } else {\n    // Insert placeholder comments if enabled\n    push(`<!--teleport start-->`);\n    push(`<!--teleport end-->`);\n  }\n}\n```\n\n## Attribute Rendering\n\n### ssrRenderAttrs\n\n```ts\n// packages/server-renderer/src/helpers/ssrRenderAttrs.ts\nexport function ssrRenderAttrs(\n  props: Record<string, unknown>,\n  tag?: string,\n): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (\n      ssrIsIgnoredKey(key) ||\n      isOn(key) ||\n      (tag === \"textarea\" && key === \"value\")\n    ) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return (\n    key === \"key\" ||\n    key === \"ref\" ||\n    key === \"innerHTML\" ||\n    key === \"textContent\"\n  );\n}\n```\n\n### ssrRenderDynamicAttr\n\nRenders dynamic attributes.\n\n```ts\nexport function ssrRenderDynamicAttr(\n  key: string,\n  value: unknown,\n  tag?: string,\n): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n\n  // Keep as-is for custom elements or SVG, otherwise convert\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag))\n      ? key\n      : propsToAttrMap[key] || key.toLowerCase();\n\n  // Handle boolean attributes\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\"\n      ? ` ${attrKey}`\n      : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(\n      `[@chibivue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`,\n    );\n    return \"\";\n  }\n}\n```\n\n### Rendering class and style\n\n```ts\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(\n  styles: Record<string, string | number> | null,\n): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n```\n\n## SSR Support for Directives\n\n```ts\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const { dir: { getSSRProps } } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n```\n\nIf a directive implements `getSSRProps`, its result is merged into props.\n\n## Escape Processing\n\nHTML escaping to prevent XSS.\n\n```ts\n// packages/server-renderer/src/helpers/ssrUtils.ts\nconst escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n```\n\n## Usage Example\n\n```ts\nimport { createApp } from \"@chibivue/runtime-dom\";\nimport { renderToString } from \"@chibivue/server-renderer\";\n\nconst App = {\n  setup() {\n    return { message: \"Hello SSR!\" };\n  },\n  template: `<div>{{ message }}</div>`,\n};\n\nconst app = createApp(App);\n\n// Render on server side\nconst html = await renderToString(app);\nconsole.log(html); // <div>Hello SSR!</div>\n```\n\n## Processing Flow\n\n```\nrenderToString(app)\n  ↓\ncreateVNode(app._component, app._props)\n  ↓\nrenderComponentVNode(vnode)\n  ├── createComponentInstance()\n  ├── setupComponent()\n  └── renderComponentSubTree()\n      ├── createBuffer()\n      ├── instance.render() or comp()\n      └── renderVNode(push, root, instance)\n          ├── Text → escapeHtml(children)\n          ├── Comment → <!--...-->\n          ├── Fragment → <!--[--> ... <!--]-->\n          ├── Element → renderElementVNode()\n          │   ├── <tag + ssrRenderAttrs(props) + >\n          │   ├── children processing\n          │   └── </tag>\n          └── Component → renderComponentVNode() (recursive)\n  ↓\nunrollBuffer(buffer)\n  ↓\nHTML string\n```\n\n## Summary\n\nThe SSR implementation of chibivue consists of the following elements:\n\n1. **SSRBuffer**: Buffer system for efficient string building (automatic string concatenation, async support)\n2. **renderComponentVNode**: Converts component VNodes to HTML (async setup support)\n3. **renderVNode**: Rendering branching according to each VNode type\n4. **renderElementVNode**: Stringifying HTML elements (void tags, special properties support)\n5. **ssrRenderAttrs**: Attribute rendering (class/style normalization, boolean attributes, safety checks)\n6. **Escape processing**: HTML escaping for XSS protection\n7. **Directive support**: Property injection during SSR via `getSSRProps`\n\nIn the next section, we'll learn about hydration, which \"restores\" the HTML generated by SSR on the client side.\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/010_ssr)\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/020-ssr/020-hydration.md",
    "content": "# Hydration\n\n## What is Hydration?\n\nIn the previous chapter, we learned how to render Vue components to HTML strings using `renderToString`. However, SSR-generated HTML is just static markup—event handlers and reactivity don't work.\n\nHydration is the process of \"activating\" server-generated HTML to function as a client-side Vue application.\n\n<KawaikoNote variant=\"question\" title=\"Why 'hydration'?\">\n\nThe name \"hydration\" comes from the image of \"breathing life\" into static HTML.\nJust like a dried plant comes alive when given water, we inject event handlers and reactivity into static HTML.\n\n</KawaikoNote>\n\n## Difference from Normal Mounting\n\n### Normal `createApp`\n\n```\n1. Generate VNode\n2. Create new DOM elements\n3. Insert DOM into container\n```\n\n### `createSSRApp` (Hydration)\n\n```\n1. Generate VNode\n2. Traverse existing DOM elements\n3. Associate VNode with DOM elements\n4. Attach event handlers\n```\n\n<KawaikoNote variant=\"funny\" title=\"The essence of Hydration\">\n\nHydration can be thought of as \"rendering without creating DOM.\"\nSince the DOM already exists, we just need to associate it with VNodes.\n\n</KawaikoNote>\n\n## Type Definitions\n\n### HydrateOptions\n\nDefines the options needed for Hydration.\n\n```ts\n// runtime-core/hydration.ts\nexport interface HydrateOptions {\n  patchProp: (el: Element, key: string, prevValue: any, nextValue: any) => void;\n  nextSibling: (node: Node) => Node | null;\n}\n```\n\n- `patchProp`: Function to attach properties (especially event handlers) to DOM elements\n- `nextSibling`: Function to traverse the DOM tree\n\n## createHydrationRenderer Implementation\n\n### Basic Structure\n\n```ts\n// runtime-core/hydration.ts\nexport function createHydrationRenderer(options: HydrateOptions) {\n  const { patchProp, nextSibling } = options;\n\n  function hydrate(vnode: VNode, container: Element): void {\n    const node = container.firstChild;\n    if (node) {\n      hydrateNode(node, vnode, null);\n    }\n  }\n\n  // ... other functions\n\n  return { hydrate };\n}\n```\n\nThe `hydrate` function starts from the container's first child node and traverses the VNode tree and DOM tree in parallel.\n\n### hydrateNode - Branching by Node Type\n\n```ts\nfunction hydrateNode(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  const { type, shapeFlag } = vnode;\n\n  // Important: Associate VNode with DOM element\n  vnode.el = node;\n\n  if (type === Text) {\n    // Text node: return next sibling\n    return nextSibling(node);\n  } else if (type === Comment) {\n    // Comment node: return next sibling\n    return nextSibling(node);\n  } else if (type === Fragment) {\n    // Fragment: special handling\n    return hydrateFragment(node, vnode, parentComponent);\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    // HTML element: process children too\n    return hydrateElement(node as Element, vnode, parentComponent);\n  }\n\n  return nextSibling(node);\n}\n```\n\nKey points:\n- `vnode.el = node` is the most important operation. This allows subsequent updates to reference the correct DOM element\n- Each function returns \"the next DOM node to process\"\n\n### hydrateElement - Hydrating HTML Elements\n\n```ts\nfunction hydrateElement(\n  el: Element,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  vnode.el = el;\n\n  const { props, children, shapeFlag } = vnode;\n\n  // Attach event handlers\n  if (props) {\n    for (const key in props) {\n      if (key.startsWith(\"on\") && typeof props[key] === \"function\") {\n        patchProp(el, key, null, props[key]);\n      }\n    }\n  }\n\n  // Hydrate children\n  if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n    hydrateChildren(el.firstChild, children as VNode[], parentComponent);\n  }\n\n  return nextSibling(el);\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"Only attach event handlers\">\n\nDuring Hydration, we only process event handlers (props starting with `on`).\nAttributes like `class` or `style` are already included in the HTML from SSR, so they don't need to be attached.\n\n</KawaikoNote>\n\n### hydrateChildren - Processing Children\n\n```ts\nfunction hydrateChildren(\n  node: Node | null,\n  children: VNode[],\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  for (let i = 0; i < children.length; i++) {\n    const child = normalizeVNode(children[i]);\n    if (node) {\n      node = hydrateNode(node, child, parentComponent);\n    }\n  }\n  return node;\n}\n```\n\nProcesses VNode children and DOM child nodes in order. Each `hydrateNode` returns the next sibling node, which is used to continue traversal.\n\n### hydrateFragment - Fragment Handling\n\nIn SSR, Fragments are rendered wrapped in `<!--[-->` and `<!--]-->` comment nodes.\n\n```ts\nfunction hydrateFragment(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  // Set the start comment (<!--[-->) to el\n  vnode.el = node;\n\n  // Children start after the start comment\n  let current = nextSibling(node);\n  const children = vnode.children as VNode[];\n\n  if (children && children.length > 0) {\n    current = hydrateChildren(current, children, parentComponent);\n  }\n\n  // Set the end comment (<!--]-->) to anchor\n  vnode.anchor = current;\n  return current ? nextSibling(current) : null;\n}\n```\n\n```html\n<!-- SSR output example -->\n<!--[-->\n<p>Item 1</p>\n<p>Item 2</p>\n<p>Item 3</p>\n<!--]-->\n```\n\n## createSSRApp Implementation\n\n`createSSRApp` is almost the same as regular `createApp`, but performs Hydration during mount.\n\n```ts\n// runtime-dom/index.ts\n\n// Create Hydration renderer\nconst { hydrate: hydrateVNode } = createHydrationRenderer({\n  patchProp,\n  nextSibling: nodeOps.nextSibling,\n});\n\nexport const createSSRApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n\n    // Check if container has SSR content\n    if (container.hasChildNodes()) {\n      // Execute Hydration\n      const proxy = mount(container, true /* isHydrate */);\n      return proxy;\n    } else {\n      // If no SSR content, normal mount\n      mount(container);\n    }\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n```\n\n## Processing Flow\n\n```\n[Server side]\nrenderToString(app)\n  ↓\n<div id=\"app\">\n  <button>Count: 0</button>\n</div>\n\n[Client side]\ncreateSSRApp(App).mount('#app')\n  ↓\ncontainer.hasChildNodes() → true\n  ↓\nhydrate(vnode, container)\n  ↓\nhydrateNode(button, vnode)\n  ├── vnode.el = button  ← Associate VNode with DOM\n  └── patchProp(button, 'onClick', null, handler)  ← Attach event\n  ↓\nClicking the button triggers reactivity\n```\n\n## Usage Example\n\n### Server-side\n\n```ts\n// server.ts\nimport { createApp } from '@chibivue/runtime-dom'\nimport { renderToString } from '@chibivue/server-renderer'\nimport App from './App.vue'\n\nconst app = createApp(App)\nconst html = await renderToString(app)\n\n// Send HTML to client\nres.send(`\n  <!DOCTYPE html>\n  <html>\n    <body>\n      <div id=\"app\">${html}</div>\n      <script src=\"/client.js\"></script>\n    </body>\n  </html>\n`)\n```\n\n### Client-side\n\n```ts\n// client.ts\nimport { createSSRApp } from '@chibivue/runtime-dom'\nimport App from './App.vue'\n\n// Use createSSRApp (not createApp)\nconst app = createSSRApp(App)\napp.mount('#app')\n```\n\n### App Component\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { ref } from '@chibivue/runtime-core'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n\n<template>\n  <button @click=\"increment\">Count: {{ count }}</button>\n</template>\n```\n\n## Hydration Mismatch\n\nDuring Hydration, the HTML generated by SSR must match the VNode generated on the client. If they don't match, a \"Hydration mismatch\" occurs.\n\n### Common Causes\n\n1. **Date/random numbers**: `new Date()` or `Math.random()` produce different values on server and client\n2. **Browser-specific APIs**: `window` or `localStorage` don't exist on the server\n3. **Conditional branches**: Different code paths are taken on server and client\n\n### Solutions\n\n```vue\n<script setup>\nimport { ref, onMounted } from '@chibivue/runtime-core'\n\n// Same initial value on server and client\nconst clientOnly = ref(false)\n\n// Update only on client side\nonMounted(() => {\n  clientOnly.value = true\n})\n</script>\n\n<template>\n  <div v-if=\"clientOnly\">\n    This content is only shown on client\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"Watch out for mismatches!\">\n\nWhen a Hydration mismatch occurs, Vue will warn, and in the worst case, the DOM may break.\nBe careful to ensure server and client produce the same output.\n\n</KawaikoNote>\n\n## Future Extensions\n\nThe current implementation is minimal, but Vue itself has features like:\n\n1. **Hydration mismatch detection**: Detect server/client inconsistencies in development mode\n2. **Partial Hydration**: Hydrate only necessary parts (performance optimization)\n3. **Optimization with PatchFlags**: Skip Hydration for static nodes\n4. **Async component Hydration**: Integration with `Suspense`\n\n<KawaikoNote variant=\"surprise\" title=\"Hydration complete!\">\n\nNow we have all the pieces for SSR.\nBy using `renderToString` for server-side rendering and\n`createSSRApp` for Hydration,\nwe can achieve a complete SSR application.\n\n</KawaikoNote>\n\n## Summary\n\nThe Hydration implementation consists of:\n\n1. **createHydrationRenderer**: Creates a renderer for Hydration\n2. **hydrateNode**: Branches processing based on VNode type\n3. **hydrateElement**: HTML elements and event handler attachment\n4. **hydrateChildren**: Recursive processing of children\n5. **hydrateFragment**: Processing Fragments (areas enclosed by comment nodes)\n6. **createSSRApp**: Application factory with Hydration support\n\nThe essence of Hydration is \"associating VNodes with existing DOM without recreating it.\" This enables both the fast initial display of SSR and the rich interactivity of SPAs.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/020-ssr/030-compiler-ssr.md",
    "content": "# Compiler SSR\n\n## What is the SSR Compiler?\n\nThe SSR compiler (`@chibivue/compiler-ssr`) is a package that compiles templates into SSR-optimized code.\n\nWhile normal client-side compilation outputs code that generates VNodes, the SSR compiler outputs code that directly generates HTML strings. This improves rendering efficiency on the server side.\n\n<KawaikoNote variant=\"base\" title=\"Client vs SSR Difference\">\n\nOn the client side:\n```js\n// Returns VNode\nreturn _createElementVNode(\"div\", { class: \"hello\" }, \"Hello\")\n```\n\nIn SSR:\n```js\n// Directly push HTML string\n_push(`<div class=\"hello\">Hello</div>`)\n```\n\nSSR is efficient because it generates strings directly without going through VNodes!\n\n</KawaikoNote>\n\n## Package Structure\n\n```\npackages/compiler-ssr/src/\n├── index.ts                    # Main entry point\n├── runtimeHelpers.ts           # SSR helper function definitions\n├── ssrCodegenTransform.ts      # SSR code generation transform\n└── transforms/\n    ├── ssrTransformElement.ts   # Element transformation\n    ├── ssrTransformComponent.ts # Component transformation\n    ├── ssrVIf.ts               # v-if transformation\n    └── ssrVFor.ts              # v-for transformation\n```\n\n## Compilation Flow\n\nSSR compilation follows these steps:\n\n1. **Parse**: Convert template to AST (using `parse` from `@chibivue/compiler-dom`)\n2. **Transform**: Apply SSR NodeTransforms\n3. **SSR Codegen Transform**: Convert AST to SSR code generation nodes\n4. **Code Generation**: Generate final JavaScript code\n\n```ts\n// packages/compiler-ssr/src/index.ts\nexport function compile(source: string | RootNode, options: CompilerOptions = {}): CodegenResult {\n  const ast = typeof source === \"string\" ? baseParse(source, options) : source;\n\n  transform(ast, {\n    ...options,\n    nodeTransforms: [\n      ssrTransformIf,\n      ssrTransformFor,\n      transformExpression,\n      ssrTransformElement,\n      ssrTransformComponent,\n      ...(options.nodeTransforms || []),\n    ],\n  });\n\n  // Convert template AST to SSR codegen AST\n  ssrCodegenTransform(ast, options);\n\n  return generate(ast, options);\n}\n```\n\n## SSR Transform Context\n\nThe context used in SSR transformation.\n\n```ts\n// packages/compiler-ssr/src/ssrCodegenTransform.ts\nexport interface SSRTransformContext {\n  root: RootNode;\n  options: CompilerOptions;\n  body: (JSChildNode | IfStatement)[];\n  helpers: Set<symbol>;\n  onError: (error: Error) => void;\n  helper<T extends symbol>(name: T): T;\n  pushStringPart(part: TemplateLiteral[\"elements\"][0]): void;\n  pushStatement(statement: IfStatement | CallExpression): void;\n}\n```\n\n### pushStringPart\n\nAdds a string part to the buffer. Consecutive strings are automatically concatenated.\n\n```ts\npushStringPart(part) {\n  if (!currentString) {\n    const currentCall = createCallExpression(`_push`);\n    body.push(currentCall);\n    currentString = createTemplateLiteral([]);\n    currentCall.arguments.push(currentString);\n  }\n  const bufferedElements = currentString.elements;\n  const lastItem = bufferedElements[bufferedElements.length - 1];\n  if (isString(part) && isString(lastItem)) {\n    // Concatenate consecutive strings\n    bufferedElements[bufferedElements.length - 1] += part;\n  } else {\n    bufferedElements.push(part);\n  }\n}\n```\n\n### pushStatement\n\nAdds control flow statements (if/for) to the buffer.\n\n```ts\npushStatement(statement) {\n  // Close current string buffer\n  currentString = null;\n  body.push(statement);\n}\n```\n\n## Element Transformation\n\n### ssrTransformElement\n\nTransforms HTML elements into SSR code.\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformElement.ts\nexport const ssrTransformElement: NodeTransform = (node, context) => {\n  if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.ELEMENT) {\n    return;\n  }\n\n  return function ssrPostTransformElement() {\n    const openTag: TemplateLiteral[\"elements\"] = [`<${node.tag}`];\n\n    // Process attributes\n    for (const prop of node.props) {\n      if (prop.type === NodeTypes.ATTRIBUTE) {\n        openTag.push(` ${prop.name}=\"${escapeHtml(prop.value.content)}\"`);\n      } else if (prop.type === NodeTypes.DIRECTIVE) {\n        // Handle v-bind\n        if (prop.name === \"bind\" && prop.arg && prop.exp) {\n          // Process class, style, and other attributes\n        }\n      }\n    }\n\n    node.ssrCodegenNode = createTemplateLiteral(openTag);\n  };\n};\n```\n\n#### Attribute Binding\n\n- **Static attributes**: Output directly as strings\n- **v-bind:class**: Use `ssrRenderClass` helper\n- **v-bind:style**: Use `ssrRenderStyle` helper\n- **Other dynamic attributes**: Use `ssrRenderAttr` or `ssrRenderDynamicAttr`\n\n### ssrProcessElement\n\nProcesses transformed elements to generate code.\n\n```ts\nexport function ssrProcessElement(node: PlainElementNode, context: SSRTransformContext): void {\n  // Output opening tag\n  for (const element of node.ssrCodegenNode!.elements) {\n    context.pushStringPart(element);\n  }\n  context.pushStringPart(`>`);\n\n  // Handle v-html\n  const vHtml = node.props.find(p => p.type === NodeTypes.DIRECTIVE && p.name === \"html\");\n  if (vHtml && vHtml.exp) {\n    context.pushStringPart(vHtml.exp);\n  } else if (node.children.length) {\n    processChildren(node, context);\n  }\n\n  // Closing tag (except void elements)\n  if (!isVoidTag(node.tag)) {\n    context.pushStringPart(`</${node.tag}>`);\n  }\n}\n```\n\n## Component Transformation\n\nComponents are rendered at runtime through `ssrRenderComponent`.\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformComponent.ts\nexport function ssrProcessComponent(\n  node: ComponentNode,\n  context: SSRTransformContext,\n  parent: { children: any[] },\n): void {\n  const vnodeCall = createCallExpression(context.helper(SSR_RENDER_VNODE), [\n    `_push`,\n    createCallExpression(context.helper(SSR_RENDER_COMPONENT), [\n      createSimpleExpression(`_component_${node.tag}`, false),\n      // props\n      node.props.length ? /* props object */ : createSimpleExpression(`null`, false),\n      // slots\n      createSimpleExpression(`null`, false),\n      // parent component\n      `_parent`,\n    ]),\n    `_parent`,\n  ]);\n\n  context.pushStatement(vnodeCall);\n}\n```\n\n## v-if Transformation\n\nv-if is transformed into JavaScript if statements.\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVIf.ts\nexport function ssrProcessIf(node: IfNode, context: SSRTransformContext): void {\n  const [rootBranch] = node.branches;\n  const ifStatement = createIfStatement(\n    rootBranch.condition!,\n    processIfBranch(rootBranch, context),\n  );\n  context.pushStatement(ifStatement);\n\n  let currentIf = ifStatement;\n  for (let i = 1; i < node.branches.length; i++) {\n    const branch = node.branches[i];\n    const branchBlockStatement = processIfBranch(branch, context);\n    if (branch.condition) {\n      // else-if\n      currentIf = currentIf.alternate = createIfStatement(branch.condition, branchBlockStatement);\n    } else {\n      // else\n      currentIf.alternate = branchBlockStatement;\n    }\n  }\n\n  // Output empty comment if no else\n  if (!currentIf.alternate) {\n    currentIf.alternate = createBlockStatement([createCallExpression(`_push`, [\"`<!---->`\"])]);\n  }\n}\n```\n\nInput:\n```html\n<div v-if=\"show\">Visible</div>\n<div v-else>Hidden</div>\n```\n\nOutput:\n```js\nif (show) {\n  _push(`<div>Visible</div>`)\n} else {\n  _push(`<div>Hidden</div>`)\n}\n```\n\n## v-for Transformation\n\nv-for is transformed using the `ssrRenderList` helper.\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVFor.ts\nexport function ssrProcessFor(node: ForNode, context: SSRTransformContext): void {\n  const renderLoop = createFunctionExpression(createForLoopParams(node.parseResult));\n  renderLoop.body = processChildrenAsStatement(node, context);\n\n  // Fragment markers\n  context.pushStringPart(`<!--[-->`);\n  context.pushStatement(\n    createCallExpression(context.helper(SSR_RENDER_LIST), [node.source, renderLoop]),\n  );\n  context.pushStringPart(`<!--]-->`);\n}\n```\n\nInput:\n```html\n<div v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</div>\n```\n\nOutput:\n```js\n_push(`<!--[-->`)\n_ssrRenderList(items, (item) => {\n  _push(`<div>${_ssrInterpolate(item.name)}</div>`)\n})\n_push(`<!--]-->`)\n```\n\n## SSR Helpers\n\nThe SSR compiler uses the following runtime helpers, provided by `@chibivue/server-renderer`.\n\n```ts\n// packages/compiler-ssr/src/runtimeHelpers.ts\nexport const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`);\nexport const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`);\nexport const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`);\nexport const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`);\nexport const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`);\nexport const SSR_RENDER_DYNAMIC_ATTR: unique symbol = Symbol(`ssrRenderDynamicAttr`);\nexport const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`);\nexport const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(`ssrIncludeBooleanAttr`);\nexport const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`);\nexport const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`);\n```\n\n### Helper Roles\n\n| Helper | Role |\n|--------|------|\n| `ssrInterpolate` | Escape text interpolation |\n| `ssrRenderAttrs` | Render object-format attributes |\n| `ssrRenderClass` | Render class |\n| `ssrRenderStyle` | Render style |\n| `ssrRenderList` | v-for iteration |\n| `ssrRenderComponent` | Create component VNode |\n| `ssrRenderVNode` | Convert VNode to HTML string |\n\n## SFC Integration\n\ncompiler-sfc supports compilation in SSR mode.\n\n```ts\n// packages/compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  compiler,\n  compilerOptions,\n  id,\n  scoped,\n  ssr = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = (compiler || defaultCompiler).compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n  return { code, ast, source, preamble };\n}\n```\n\nSpecifying `ssr: true` automatically uses the SSR compiler.\n\n## Generated Code Example\n\nInput template:\n```html\n<div class=\"container\">\n  <h1>{{ title }}</h1>\n  <ul>\n    <li v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</li>\n  </ul>\n</div>\n```\n\nGenerated code:\n```js\nimport { ssrInterpolate as _ssrInterpolate, ssrRenderList as _ssrRenderList } from 'chibivue/server-renderer'\n\nfunction ssrRender(_ctx, _push, _parent, _attrs) {\n  _push(`<div class=\"container\"><h1>${_ssrInterpolate(_ctx.title)}</h1><ul><!--[-->`)\n  _ssrRenderList(_ctx.items, (item) => {\n    _push(`<li>${_ssrInterpolate(item.name)}</li>`)\n  })\n  _push(`<!--]--></ul></div>`)\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR Compiler Benefits\">\n\nUsing the SSR compiler provides:\n- No VNode overhead\n- Efficient string generation with template literals\n- Static parts are output directly as strings\n\nThese improve server-side rendering performance!\n\n</KawaikoNote>\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/030-builtins/010-keep-alive.md",
    "content": "# KeepAlive\n\n## What is KeepAlive\n\n`<KeepAlive>` is a built-in component that caches and reuses component instances without destroying them. Normally, when components are switched, the old component is unmounted and its state is lost. However, by using KeepAlive, you can switch between components while preserving their state.\n\n<KawaikoNote variant=\"question\" title=\"Why is KeepAlive needed?\">\n\nFor example, imagine a screen with tab switching where you have a form being filled out in one tab.\nIf you switch to another tab and come back, it would be frustrating if the input content disappeared.\nKeepAlive addresses this need to \"preserve state\"!\n\n</KawaikoNote>\n\nMain use cases:\n\n1. **Tab switching**: Preserve input content when switching tabs during form entry\n2. **Routing**: Preserve scroll position and input state during page navigation\n3. **Performance**: Avoid re-rendering frequently switched components\n\n## Basic Usage\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentTab\" />\n  </KeepAlive>\n</template>\n```\n\n## Implementation Overview\n\n### Props Definition\n\n```ts\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n```\n\n- **include**: Component names to cache (only those included are cached)\n- **exclude**: Component names to exclude from caching (those included are not cached)\n- **max**: Maximum number to cache (oldest ones are removed using LRU algorithm)\n\n### KeepAliveContext\n\nThe KeepAlive component has a special context for interacting with the renderer.\n\n```ts\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n```\n\n- **activate**: Bring a cached component back to display\n- **deactivate**: Hide a component and cache it\n\n## Core Logic Implementation\n\n### Cache Management\n\n```ts\nconst cache: Map<any, VNode> = new Map();\nconst keys: Set<any> = new Set();\nlet current: VNode | null = null;\n\n// Hidden container for storing inactive components\nconst storageContainer = instance.renderer.o.createElement(\"div\");\n```\n\nKeepAlive caches component VNodes using a `cache` Map. The `keys` Set is used for order management in the LRU (Least Recently Used) algorithm.\n\n### activate Function\n\nRestores a component from the cache and displays it.\n\n```ts\ninstance.activate = (vnode, container, anchor, _parentComponent) => {\n  const instance = vnode.component!;\n  // Move from hidden container to actual container\n  move(vnode, container, anchor);\n  // Apply any props changes\n  patch(instance.vnode, vnode, container, anchor, parentComponent);\n  queuePostFlushCb(() => {\n    instance.isDeactivated = false;\n    // Call onActivated hooks\n    if (instance.a) {\n      instance.a.forEach((hook: () => void) => hook());\n    }\n  });\n};\n```\n\nKey points:\n1. Move DOM from hidden container to target container\n2. Apply props changes via patch\n3. Call `onActivated` lifecycle hook\n\n### deactivate Function\n\nHides and caches the component.\n\n```ts\ninstance.deactivate = (vnode: VNode) => {\n  // Move to hidden container (DOM is not removed)\n  move(vnode, storageContainer, null);\n  queuePostFlushCb(() => {\n    const instance = vnode.component!;\n    // Call onDeactivated hooks\n    if (instance.da) {\n      instance.da.forEach((hook: () => void) => hook());\n    }\n    instance.isDeactivated = true;\n  });\n};\n```\n\nUnlike normal unmounting, DOM elements are not removed but simply moved to the hidden container.\n\n<KawaikoNote variant=\"funny\" title=\"The Hidden Container Trick\">\n\nComponents being hidden are moved to a \"hideout\" off-screen.\nWhen needed, they're simply retrieved from the \"hideout\", saving the trouble of rebuilding!\n\n</KawaikoNote>\n\n### render Function\n\nThis is the core logic of KeepAlive.\n\n```ts\nreturn (): VNode | undefined => {\n  if (!slots.default) {\n    return undefined;\n  }\n\n  const children = slots.default();\n  const rawVNode = children[0];\n\n  // Don't cache if there are multiple children\n  if (children.length > 1) {\n    current = null;\n    return children as unknown as VNode;\n  }\n\n  // Return as-is if not a component\n  if (\n    !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n    !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n  ) {\n    current = null;\n    return rawVNode;\n  }\n\n  let vnode = rawVNode;\n  const comp = vnode.type as any;\n  const name = getComponentName(comp);\n  const { include, exclude, max } = props;\n\n  // include/exclude filtering\n  if (\n    (include && (!name || !matches(include, name))) ||\n    (exclude && name && matches(exclude, name))\n  ) {\n    current = vnode;\n    return rawVNode;\n  }\n\n  // Determine cache key\n  const key = vnode.key == null ? comp : vnode.key;\n  const cachedVNode = cache.get(key);\n\n  if (cachedVNode) {\n    // Cache hit: restore state\n    vnode.el = cachedVNode.el;\n    vnode.component = cachedVNode.component;\n    vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n    // LRU: update order since recently used\n    keys.delete(key);\n    keys.add(key);\n  } else {\n    // New cache entry\n    keys.add(key);\n    // Remove oldest if exceeding max\n    if (max && keys.size > parseInt(max as string, 10)) {\n      pruneCacheEntry(keys.values().next().value);\n    }\n  }\n\n  // Set flag to let renderer recognize KeepAlive\n  vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  current = vnode;\n  return vnode;\n};\n```\n\n### Control via ShapeFlags\n\nKeepAlive coordinates with the renderer using ShapeFlags.\n\n```ts\n// This component should be managed by KeepAlive\nvnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n\n// This component was restored from cache\nvnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n```\n\nThe renderer checks these flags and calls activate/deactivate instead of normal mount/unmount.\n\n### include/exclude Matching\n\n```ts\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n```\n\nPatterns support the following formats:\n- String (comma-separated): `\"ComponentA,ComponentB\"`\n- Regular expression: `/^Tab/`\n- Array: `[\"ComponentA\", /^Tab/]`\n\n### Cache Pruning\n\n```ts\nfunction pruneCacheEntry(key: any): void {\n  const cached = cache.get(key) as VNode;\n  // Unmount if not currently displayed\n  if (!current || !isSameVNodeType(cached, current)) {\n    unmount(cached);\n  } else if (current) {\n    // Only reset flags if currently displayed\n    resetShapeFlag(current);\n  }\n  cache.delete(key);\n  keys.delete(key);\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n```\n\n## Lifecycle Hooks\n\nComponents managed by KeepAlive can use additional lifecycle hooks:\n\n- **onActivated**: When the component becomes active\n- **onDeactivated**: When the component becomes inactive\n\n```ts\nimport { onActivated, onDeactivated } from 'vue'\n\nexport default {\n  setup() {\n    onActivated(() => {\n      console.log('activated!')\n    })\n    onDeactivated(() => {\n      console.log('deactivated!')\n    })\n  }\n}\n```\n\n## Usage Examples\n\n### Basic Usage\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### Using include/exclude\n\n```vue\n<template>\n  <!-- Cache only ComponentA and ComponentB -->\n  <KeepAlive include=\"ComponentA,ComponentB\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- Cache everything except ComponentC -->\n  <KeepAlive exclude=\"ComponentC\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- Match with regular expression -->\n  <KeepAlive :include=\"/^Tab/\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### Using max\n\n```vue\n<template>\n  <!-- Cache up to 10 components (LRU) -->\n  <KeepAlive :max=\"10\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n## Integration with Renderer\n\nKeepAlive works in close coordination with the renderer.\n\n### KeepAlive Detection in mountComponent\n\n```ts\n// packages/runtime-core/src/renderer.ts\nconst mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {\n  const instance: ComponentInternalInstance = (\n    initialVNode.component = createComponentInstance(initialVNode, parentComponent)\n  );\n\n  // For KeepAlive components, inject the renderer\n  if (isKeepAlive(initialVNode)) {\n    (instance as KeepAliveContext).renderer = {\n      p: patch,   // patch function\n      m: move,    // DOM move function\n      um: unmount, // unmount function\n      o: options,  // host options (createElement, etc.)\n    };\n  }\n\n  // ... normal mount processing\n};\n```\n\n### KEPT_ALIVE Check in processComponent\n\n```ts\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null = null,\n) => {\n  if (n1 == null) {\n    // New mount\n    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n      // Restore from cache: call activate\n      (parentComponent as KeepAliveContext).activate(\n        n2,\n        container,\n        anchor,\n        parentComponent as ComponentInternalInstance\n      );\n    } else {\n      // Normal mount\n      mountComponent(n2, container, anchor, parentComponent);\n    }\n  } else {\n    updateComponent(n1, n2);\n  }\n};\n```\n\n### SHOULD_KEEP_ALIVE Check in unmount\n\n```ts\nconst unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {\n  const { type, shapeFlag, children } = vnode;\n\n  // Components under KeepAlive management are deactivated, not removed\n  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n    (parentComponent as KeepAliveContext).deactivate(vnode);\n    return;\n  }\n\n  // Normal unmount processing\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    unmountComponent(vnode.component!);\n  }\n  // ...\n};\n```\n\n## Processing Flow\n\n```\nInitial Mount:\nKeepAlive render\n  → Get child from slot\n  → Not in cache → Add to keys\n  → Set COMPONENT_SHOULD_KEEP_ALIVE flag\n  → Return vnode\n      ↓\nprocessComponent\n  → No COMPONENT_KEPT_ALIVE → mountComponent\n  → isKeepAlive(vnode) → Inject renderer\n  → Normal component mount\n\nRestore from Cache:\nKeepAlive render\n  → Get child from slot\n  → Cache hit → Reuse el/component\n  → Add COMPONENT_KEPT_ALIVE flag\n  → Update keys order (LRU)\n  → Return vnode\n      ↓\nprocessComponent\n  → COMPONENT_KEPT_ALIVE present\n  → Call parentComponent.activate()\n      ↓\nactivate\n  → Move from hidden container to real container\n  → Apply props changes via patch\n  → instance.isDeactivated = false\n  → Call onActivated hook\n\nDeactivation:\nunmount\n  → COMPONENT_SHOULD_KEEP_ALIVE present\n  → Call parentComponent.deactivate()\n      ↓\ndeactivate\n  → Move to hidden container (DOM is not removed)\n  → instance.isDeactivated = true\n  → Call onDeactivated hook\n  → Kept in cache\n```\n\n<KawaikoNote variant=\"warning\" title=\"Watch Your Memory Usage!\">\n\nComponents cached by KeepAlive remain in memory.\nCaching too many can strain memory, so set an upper limit with the `max` property.\nIt's automatically managed with LRU (removing least recently used items)!\n\n</KawaikoNote>\n\n## Summary\n\nThe KeepAlive implementation consists of the following elements:\n\n1. **Cache System**: LRU cache using Map and Set\n2. **Hidden Container**: Holds inactive DOM (`createElement(\"div\")`)\n3. **activate/deactivate**: DOM movement and lifecycle management\n4. **ShapeFlags**: Coordination with renderer\n   - `COMPONENT_SHOULD_KEEP_ALIVE`: Call deactivate during unmount\n   - `COMPONENT_KEPT_ALIVE`: Call activate during mount\n5. **Renderer Injection**: KeepAlive holds references to patch/move/unmount functions\n6. **include/exclude/max**: Flexible cache control\n\nKeepAlive is a powerful feature that improves performance while preserving component state, but there's a trade-off with memory usage, so setting an appropriate `max` value is important.\n\n<KawaikoNote variant=\"surprise\" title=\"KeepAlive Complete!\">\n\nIt's a simple idea of \"hiding instead of removing\" components,\nbut the implementation with renderer coordination and LRU cache is quite deep!\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/020_keep_alive)\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/030-builtins/020-suspense.md",
    "content": "---\nwip: true\n---\n\n# Coming Soon\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/030-builtins/030-transition.md",
    "content": "# Transition\n\n## What is Transition?\n\n`<Transition>` is a built-in component for applying animations when showing or hiding elements and components. It works with CSS transitions/animations to achieve smooth UI transitions.\n\n<KawaikoNote variant=\"question\" title=\"Why is Transition Needed?\">\n\nWhen you toggle element visibility with `v-if`, elements appear and disappear instantly.\nWith Transition, you can easily add animations\nlike fade-in/out or slides!\n\n</KawaikoNote>\n\nMain use cases:\n\n1. **Combining with v-if / v-show**: Animations for conditional rendering\n2. **Dynamic components**: Switching animations with `<component :is>`\n3. **Route transitions**: Transition effects between pages\n\n## Basic Usage\n\n```vue\n<template>\n  <button @click=\"show = !show\">Toggle</button>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n## Implementation Overview\n\n### Props Definition\n\n```ts\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  appearFromClass?: string;\n  appearActiveClass?: string;\n  appearToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  appear?: boolean;\n  // Lifecycle hooks\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onEnterCancelled?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n  onLeaveCancelled?: (el: Element) => void;\n  // Appear hooks\n  onBeforeAppear?: (el: Element) => void;\n  onAppear?: (el: Element, done: () => void) => void;\n  onAfterAppear?: (el: Element) => void;\n  onAppearCancelled?: (el: Element) => void;\n}\n```\n\n### TransitionHooks Interface\n\n```ts\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n```\n\nThe renderer coordinates with Transition through this interface.\n\n## CSS Class Lifecycle\n\nTransition automatically adds and removes the following CSS classes:\n\n### Enter (Showing Element)\n\n1. **v-enter-from**: Start state. Added before the element is inserted, removed after 1 frame\n2. **v-enter-active**: Active state. Applied throughout the entire transition\n3. **v-enter-to**: End state. Added 1 frame after start, removed when transition ends\n\n### Leave (Hiding Element)\n\n1. **v-leave-from**: Start state. Added when leave transition starts, removed after 1 frame\n2. **v-leave-active**: Active state. Applied throughout the entire transition\n3. **v-leave-to**: End state. Added 1 frame after start, removed when transition ends\n\n```\nEnter:\n┌──────────────────────────────────────────┐\n│ v-enter-from → (1 frame) → v-enter-to   │\n│ ├─────── v-enter-active ──────────────┤ │\n└──────────────────────────────────────────┘\n\nLeave:\n┌──────────────────────────────────────────┐\n│ v-leave-from → (1 frame) → v-leave-to   │\n│ ├─────── v-leave-active ──────────────┤ │\n└──────────────────────────────────────────┘\n```\n\n## Core Logic Implementation\n\n### resolveTransitionProps\n\nParses props and generates TransitionHooks.\n\n```ts\nexport function resolveTransitionProps(\n  rawProps: TransitionProps\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    // ... other classes\n    mode = \"default\",\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  // Generate hook functions\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      callHook(onBeforeEnter, [el]);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter: makeEnterHook(false),\n    leave(el, done) {\n      // leave logic\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n```\n\n### CSS Class Management\n\n```ts\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function addTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n```\n\nThe `_vtc` (Vue Transition Classes) property tracks the currently applied transition classes.\n\n### nextFrame\n\nTo make CSS transitions work correctly, we wait 2 frames before changing classes.\n\n```ts\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n```\n\nThe first frame allows the browser to recognize the initial state, and the second frame applies the change, ensuring the transition fires reliably.\n\n<KawaikoNote variant=\"funny\" title=\"Why Wait 2 Frames?\">\n\n\"Why call `requestAnimationFrame` twice?\" you might wonder.\nThe first call tells the browser \"this is the initial state,\"\nand the second call tells it \"this is the end state,\"\nallowing the browser to recognize the transition!\n\n</KawaikoNote>\n\n### Enter Hook\n\n```ts\nconst makeEnterHook = (isAppear: boolean) => {\n  return (el: Element, done: () => void) => {\n    const hook = isAppear ? onAppear : onEnter;\n    const resolve = () => finishEnter(el, isAppear, done);\n\n    callHook(hook, [el, resolve]);\n\n    nextFrame(() => {\n      removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass);\n      addTransitionClass(el, isAppear ? appearToClass : enterToClass);\n      if (!hasExplicitCallback(hook)) {\n        whenTransitionEnds(el, type, enterDuration, resolve);\n      }\n    });\n  };\n};\n```\n\n1. Call user-defined hooks\n2. After 2 frames, remove the from class and add the to class\n3. Detect transition end and complete the process\n\n### Leave Hook\n\n```ts\nleave(el, done) {\n  const resolve = () => finishLeave(el, done);\n  addTransitionClass(el, leaveFromClass);\n  // Force reflow\n  forceReflow();\n  addTransitionClass(el, leaveActiveClass);\n\n  nextFrame(() => {\n    removeTransitionClass(el, leaveFromClass);\n    addTransitionClass(el, leaveToClass);\n    if (!hasExplicitCallback(onLeave)) {\n      whenTransitionEnds(el, type, leaveDuration, resolve);\n    }\n  });\n  callHook(onLeave, [el, resolve]);\n}\n```\n\n## Detecting Transition End\n\n### getTransitionInfo\n\nGets transition/animation information from CSS.\n\n```ts\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"]\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  // Determine whether to use transition or animation\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    // For animation\n  } else {\n    // Auto-detect\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0\n      ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION)\n      : null;\n  }\n\n  return { type, timeout, propCount, hasTransform };\n}\n```\n\n### whenTransitionEnds\n\nExecutes a callback when the transition ends.\n\n```ts\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  // If explicit timeout is provided, use it\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout);\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\"; // \"transitionend\" or \"animationend\"\n  let ended = 0;\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  // Timeout fallback\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n```\n\nKey points:\n- Monitors `transitionend` / `animationend` events\n- Waits for as many events as there are properties\n- Timeout fallback (insurance in case the event doesn't fire)\n- `_endId` cancels old transitions\n\n### forceReflow\n\nForces a reflow to ensure CSS transitions fire reliably.\n\n```ts\nexport function forceReflow(): number {\n  return document.body.offsetHeight;\n}\n```\n\nReading `offsetHeight` forces the browser to recalculate styles.\n\n<KawaikoNote variant=\"warning\" title=\"Why Force a Reflow?\">\n\nEven when CSS classes are added consecutively, the browser\nmay batch style recalculations for optimization.\nReading `offsetHeight` forces it to \"calculate now!\"\n\n</KawaikoNote>\n\n## Transition Component Body\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // Set transition hooks on the VNode\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\nTransition itself doesn't render any DOM elements; it just attaches a `transition` property to the child VNode. The renderer sees this property and calls the hooks.\n\n## Usage Examples\n\n### Basic Fade\n\n```vue\n<template>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n### Slide Animation\n\n```vue\n<template>\n  <Transition name=\"slide\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.slide-enter-active,\n.slide-leave-active {\n  transition: all 0.3s ease;\n}\n.slide-enter-from {\n  transform: translateX(-100%);\n  opacity: 0;\n}\n.slide-leave-to {\n  transform: translateX(100%);\n  opacity: 0;\n}\n</style>\n```\n\n### JavaScript Hooks\n\n```vue\n<template>\n  <Transition\n    @before-enter=\"onBeforeEnter\"\n    @enter=\"onEnter\"\n    @after-enter=\"onAfterEnter\"\n    @leave=\"onLeave\"\n    :css=\"false\"\n  >\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<script setup>\nfunction onBeforeEnter(el) {\n  el.style.opacity = 0;\n}\n\nfunction onEnter(el, done) {\n  // Use an animation library like GSAP\n  gsap.to(el, {\n    opacity: 1,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n\nfunction onLeave(el, done) {\n  gsap.to(el, {\n    opacity: 0,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n</script>\n```\n\n### Explicit Duration\n\n```vue\n<template>\n  <!-- enter: 300ms, leave: 500ms -->\n  <Transition name=\"fade\" :duration=\"{ enter: 300, leave: 500 }\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n```\n\n## Integration with VNode\n\n### VNode.transition Property\n\nVNode has a `transition` property that stores TransitionHooks.\n\n```ts\n// packages/runtime-core/src/vnode.ts\nexport interface VNode<HostNode = any> {\n  // ... other properties\n\n  // transition\n  transition: any | null;\n}\n```\n\n### Setting in Transition Component\n\nThe Transition component sets the `transition` property on the child VNode.\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // Set transition hooks on the VNode\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\n### Processing in the Renderer\n\nThe renderer detects the VNode's `transition` property and calls hooks at appropriate times:\n\n1. **When inserting element**: `beforeEnter` → DOM insertion → `enter`\n2. **When removing element**: `leave` → DOM removal\n\n```ts\n// Conceptual processing flow\nconst mountElement = (vnode, container, anchor) => {\n  const el = createElement(vnode.type);\n\n  // Call beforeEnter if there's a transition\n  if (vnode.transition) {\n    vnode.transition.beforeEnter(el);\n  }\n\n  // Insert into DOM\n  insert(el, container, anchor);\n\n  // Call enter if there's a transition\n  if (vnode.transition) {\n    vnode.transition.enter(el);\n  }\n};\n\nconst unmountElement = (vnode) => {\n  const el = vnode.el;\n\n  // Call leave if there's a transition\n  if (vnode.transition) {\n    vnode.transition.leave(el, () => {\n      // Remove from DOM after leave completes\n      remove(el);\n    });\n  } else {\n    remove(el);\n  }\n};\n```\n\n## Processing Flow\n\n```\nTransition component render\n  ↓\nGenerate TransitionHooks with resolveTransitionProps\n  ↓\nchild.transition = innerProps\n  ↓\nRenderer mountElement\n  ├── beforeEnter(el)\n  │   └── Add enterFromClass/enterActiveClass\n  ├── insert(el, container)\n  └── enter(el, done)\n      └── In nextFrame\n          ├── Remove enterFromClass\n          ├── Add enterToClass\n          └── Wait for completion with whenTransitionEnds\n              └── done() calls finishEnter\n\nRenderer unmountElement\n  └── transition.leave(el, remove)\n      ├── Add leaveFromClass\n      ├── forceReflow()\n      ├── Add leaveActiveClass\n      └── In nextFrame\n          ├── Remove leaveFromClass\n          ├── Add leaveToClass\n          └── Wait for completion with whenTransitionEnds\n              └── remove() removes from DOM\n```\n\n## Summary\n\nThe Transition implementation consists of the following elements:\n\n1. **CSS class management**: Add/remove classes at each phase of enter/leave\n2. **nextFrame**: Wait 2 frames to guarantee transition fires\n3. **forceReflow**: Force reflow for style recalculation\n4. **whenTransitionEnds**: Monitor transitionend/animationend events\n5. **JavaScript hooks**: Support for animations without CSS\n6. **VNode.transition**: Property for the renderer to call hooks\n\nTransition works closely with CSS transitions/animations and is implemented with a deep understanding of the browser's rendering pipeline.\n\n<KawaikoNote variant=\"surprise\" title=\"Transition Complete!\">\n\nNot just CSS class manipulation, but frame timing control and reflow management too -\nthis implementation requires a deep understanding of browser internals.\nSurprisingly deep, isn't it!\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/030_transition)\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/040-optimizations/010-static-hoisting.md",
    "content": "# Static Hoisting\n\n## What is Static Hoisting\n\nStatic Hoisting is one of the optimization techniques during template compilation. It detects static nodes (nodes without reactive dependencies) in the template and \"hoists\" them outside the render function, improving performance during re-rendering.\n\n<KawaikoNote variant=\"question\" title=\"Why is it called hoisting?\">\n\nIt's the same concept as JavaScript's \"variable hoisting\".\nBy \"lifting\" static code from inside the render function to outside,\nwe no longer need to regenerate it every time the function is called!\n\n</KawaikoNote>\n\n### Effects of Optimization\n\n1. **Skip VNode Generation**: Static nodes are generated only once and reused\n2. **Reduced Memory Usage**: The same VNode objects are reused\n3. **Skip Patch Processing**: Static nodes can be excluded from comparison\n\n## Comparison Before and After Optimization\n\n### Template\n\n```vue\n<template>\n  <div>\n    <h1>Hello World</h1>\n    <p>{{ message }}</p>\n  </div>\n</template>\n```\n\n### Compilation Result Without Optimization\n\n```js\nfunction render() {\n  return h('div', null, [\n    h('h1', null, 'Hello World'),  // Generated every time\n    h('p', null, message.value)\n  ])\n}\n```\n\n### After Applying Static Hoisting\n\n```js\nconst _hoisted_1 = h('h1', null, 'Hello World')  // Generated once outside\n\nfunction render() {\n  return h('div', null, [\n    _hoisted_1,  // Reference is reused\n    h('p', null, message.value)\n  ])\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"Dramatic Before and After!\">\n\nInstead of generating VNodes every time, we just reuse the VNode generated once.\nThe more unchanging parts like headers and footers there are, the greater the effect!\n\n</KawaikoNote>\n\n## Implementation Overview\n\n### ConstantTypes\n\nAn enum representing the static nature of nodes.\n\n```ts\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,    // Dynamic (cannot be hoisted)\n  CAN_SKIP_PATCH = 1,  // Can skip patch processing\n  CAN_HOIST = 2,       // Can be hoisted\n  CAN_STRINGIFY = 3,   // Can be stringified (further optimization possible)\n}\n```\n\n### hoistStatic Function\n\nCalled after the transform phase to detect and hoist static nodes.\n\n```ts\nexport function hoistStatic(root: RootNode, context: TransformContext): void {\n  walk(root, context, new Map());\n}\n```\n\n### walk Function\n\nRecursively traverses the AST and detects nodes that can be hoisted.\n\n```ts\nfunction walk(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): void {\n  const { children } = node as RootNode | ElementNode;\n  if (!children) return;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (\n      child.type === NodeTypes.ELEMENT &&\n      child.tagType === 0 // Only plain elements\n    ) {\n      const constantType = getConstantType(child, context, resultCache);\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          // Can be hoisted\n          const codegenNode = child.codegenNode as VNodeCall | undefined;\n          if (codegenNode && codegenNode.type === NodeTypes.VNODE_CALL) {\n            codegenNode.isStatic = true;\n            context.hoists.push(codegenNode);\n            // Replace codegenNode with hoisted reference\n            child.codegenNode = context.hoist(codegenNode) as VNodeCall;\n          }\n        }\n      } else {\n        // If dynamic, recursively check children\n        walk(child, context, resultCache);\n      }\n    }\n  }\n}\n```\n\nKey points:\n1. Only plain elements (not components) are targeted\n2. Static nodes are added to `context.hoists`\n3. The original `codegenNode` is replaced with a reference to `_hoisted_N`\n4. Dynamic nodes have their children recursively checked\n\n### getConstantType Function\n\nDetermines whether a node is static.\n\n```ts\nexport function getConstantType(\n  node: TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): ConstantTypes {\n  // Check cache\n  const cached = resultCache.get(node);\n  if (cached !== undefined) {\n    return cached;\n  }\n\n  if (node.type === NodeTypes.ELEMENT) {\n    // Components cannot be hoisted\n    if (node.tagType !== 0) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    const element = node as PlainElementNode;\n    const codegenNode = element.codegenNode;\n\n    if (!codegenNode || codegenNode.type !== NodeTypes.VNODE_CALL) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    // Check for dynamic props\n    if (codegenNode.props) {\n      const propsType = codegenNode.props.type;\n      if (propsType !== NodeTypes.JS_OBJECT_EXPRESSION) {\n        resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n\n      const properties = codegenNode.props.properties;\n      for (let i = 0; i < properties.length; i++) {\n        const { key, value } = properties[i];\n        // Not possible if both key and value are not static\n        if (key.type !== NodeTypes.SIMPLE_EXPRESSION || !key.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n        if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // Recursively check child elements\n    if (element.children) {\n      for (let i = 0; i < element.children.length; i++) {\n        const child = element.children[i];\n        const childType = getConstantType(child, context, resultCache);\n        if (childType === ConstantTypes.NOT_CONSTANT) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // Not possible if there are directives\n    if (element.props && element.props.length > 0) {\n      for (const prop of element.props) {\n        if (prop.type === NodeTypes.DIRECTIVE) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    resultCache.set(node, ConstantTypes.CAN_HOIST);\n    return ConstantTypes.CAN_HOIST;\n  }\n\n  // Text nodes can be hoisted\n  if (node.type === NodeTypes.TEXT) {\n    resultCache.set(node, ConstantTypes.CAN_STRINGIFY);\n    return ConstantTypes.CAN_STRINGIFY;\n  }\n\n  // Interpolations ({{ }}) are dynamic\n  if (node.type === NodeTypes.INTERPOLATION) {\n    resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n    return ConstantTypes.NOT_CONSTANT;\n  }\n\n  resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n  return ConstantTypes.NOT_CONSTANT;\n}\n```\n\nDetermination logic:\n1. **Components**: Always dynamic (props and slots may change)\n2. **Dynamic props**: Dynamic if there are bindings (`:class`, `:style`, etc.)\n3. **Directives**: Dynamic if there are `v-if`, `v-for`, etc.\n4. **Interpolation**: `{{ message }}` is dynamic\n5. **Children**: If even one child is dynamic, the parent is also dynamic\n6. **Static text/attributes**: Can be hoisted\n\n### Code Generation\n\n```ts\nfunction genHoists(\n  hoists: (TemplateChildNode | ExpressionNode)[],\n  context: CodegenContext\n) {\n  const { push, newline } = context;\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context);\n      newline();\n    }\n  }\n}\n```\n\nNodes accumulated in the `hoists` array are generated as constants before the render function.\n\n### TransformContext's hoist Method\n\n```ts\nhoist(exp) {\n  context.hoists.push(exp);\n  const identifier = createSimpleExpression(\n    `_hoisted_${context.hoists.length}`,\n    false,\n  );\n  return identifier;\n}\n```\n\nAdds the original node to the `hoists` array and returns an identifier `_hoisted_N`. This is referenced within the render function.\n\n## Examples of Hoistable Nodes\n\n```vue\n<template>\n  <!-- Can be hoisted -->\n  <div class=\"static\">Static content</div>\n  <img src=\"/logo.png\" alt=\"Logo\">\n  <p>Fixed text</p>\n\n  <!-- Cannot be hoisted -->\n  <div :class=\"dynamicClass\">Dynamic</div>\n  <p>{{ message }}</p>\n  <div v-if=\"show\">Conditional</div>\n  <MyComponent />\n</template>\n```\n\n## Invocation in the Transform Phase\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions): void {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n\n  // Execute if hoistStatic option is enabled\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n\n  createRootCodegen(root, context);\n  root.components = [...context.components];\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.hoists = context.hoists;\n}\n```\n\n## Options\n\n```ts\nexport interface TransformOptions {\n  hoistStatic?: boolean;  // Enable static hoisting\n  // ...\n}\n```\n\n## Generated Code Example\n\nInput template:\n```vue\n<template>\n  <div>\n    <header>\n      <h1>My App</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ content }}</p>\n    </main>\n  </div>\n</template>\n```\n\nGenerated code:\n```js\nimport { createVNode as _createVNode, toDisplayString as _toDisplayString } from 'vue'\n\n// Static nodes are hoisted outside\nconst _hoisted_1 = _createVNode(\"header\", null, [\n  _createVNode(\"h1\", null, \"My App\"),\n  _createVNode(\"nav\", null, [\n    _createVNode(\"a\", { href: \"/home\" }, \"Home\"),\n    _createVNode(\"a\", { href: \"/about\" }, \"About\")\n  ])\n])\n\nfunction render(_ctx) {\n  return _createVNode(\"div\", null, [\n    _hoisted_1,  // Reference is reused\n    _createVNode(\"main\", null, [\n      _createVNode(\"p\", null, _toDisplayString(_ctx.content))  // Dynamic part\n    ])\n  ])\n}\n```\n\n## Summary\n\nThe Static Hoisting implementation consists of the following elements:\n\n1. **ConstantTypes**: An enum representing the static level of nodes\n2. **getConstantType**: Determines whether a node is static\n3. **walk**: Traverses the AST and detects hoistable nodes\n4. **hoist**: Adds nodes to the hoist array and returns a reference\n5. **genHoists**: Generates code for hoisted nodes\n\nThis optimization significantly improves re-rendering performance for large templates with many static contents. It is particularly effective for unchanged UI parts such as headers, footers, and sidebars.\n\n<KawaikoNote variant=\"surprise\" title=\"Static Hoisting Complete!\">\n\nThe compiler automatically optimizes by determining \"this part won't change\".\nThis is a strength unique to template-based frameworks!\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/040_static_hoisting)\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/040-optimizations/020-patch-flags.md",
    "content": "# Patch Flags\n\n## What are Patch Flags?\n\nPatch Flags are optimization hints generated by the compiler. By attaching flags to VNodes, the runtime's diffing algorithm can skip unnecessary checks and improve performance.\n\n<KawaikoNote variant=\"question\" title=\"Why does the compiler optimize?\">\n\nWhen humans write templates, they understand \"this part is dynamic\" and \"this part is static\",\nbut traditional Virtual DOM doesn't know this. By having the compiler convey this information to the runtime,\nunnecessary comparisons can be eliminated!\n\n</KawaikoNote>\n\n### How the Optimization Works\n\nIn normal Virtual DOM diffing, all properties and child elements need to be compared. However, the compiler knows \"which parts are dynamic\" at the template analysis stage. By embedding this information as Patch Flags in VNodes, the runtime can check only the parts that might change.\n\n## PatchFlags Definition\n\n```ts\nexport const enum PatchFlags {\n  /**\n   * Element with dynamic textContent\n   */\n  TEXT = 1,\n\n  /**\n   * Element with dynamic class binding\n   */\n  CLASS = 1 << 1,  // 2\n\n  /**\n   * Element with dynamic style\n   */\n  STYLE = 1 << 2,  // 4\n\n  /**\n   * Element with dynamic props other than class/style\n   */\n  PROPS = 1 << 3,  // 8\n\n  /**\n   * Element with props that have dynamic keys\n   */\n  FULL_PROPS = 1 << 4,  // 16\n\n  /**\n   * Props processing needed during hydration\n   */\n  NEED_HYDRATION = 1 << 5,  // 32\n\n  /**\n   * Fragment with children whose order doesn't change\n   */\n  STABLE_FRAGMENT = 1 << 6,  // 64\n\n  /**\n   * Fragment with keyed children\n   */\n  KEYED_FRAGMENT = 1 << 7,  // 128\n\n  /**\n   * Fragment with unkeyed children\n   */\n  UNKEYED_FRAGMENT = 1 << 8,  // 256\n\n  /**\n   * Patch needed for non-props (ref, directives, etc.)\n   */\n  NEED_PATCH = 1 << 9,  // 512\n\n  /**\n   * Component with dynamic slots\n   */\n  DYNAMIC_SLOTS = 1 << 10,  // 1024\n\n  /**\n   * For development: Fragment with comment at root\n   */\n  DEV_ROOT_FRAGMENT = 1 << 11,  // 2048\n\n  // Special flags (negative integers)\n\n  /**\n   * Cached static VNode\n   */\n  CACHED = -1,\n\n  /**\n   * Hint to exit optimized mode\n   */\n  BAIL = -2,\n}\n```\n\n## Combining with Bitwise Operations\n\nPatch Flags are designed as bit flags, allowing multiple flags to be combined.\n\n```ts\n// Combining flags\nconst flag = PatchFlags.TEXT | PatchFlags.CLASS;  // 3 (0b11)\n\n// Checking flags\nif (flag & PatchFlags.TEXT) {\n  // TEXT flag is set\n}\n\nif (flag & PatchFlags.CLASS) {\n  // CLASS flag is set\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"The magic of bitwise operations\">\n\n`1 << 1` is `2`, `1 << 2` is `4`... just shifting bits creates independent flags.\nCombine with `|` (OR), check with `&` (AND). Simple but super efficient!\n\n</KawaikoNote>\n\n## Examples of Generation from Templates\n\n### Dynamic Text\n\n```vue\n<template>\n  <p>{{ message }}</p>\n</template>\n```\n\nGenerated code:\n```js\n// patchFlag = 1 (TEXT)\ncreateVNode(\"p\", null, toDisplayString(message), 1 /* TEXT */)\n```\n\n### Dynamic Class\n\n```vue\n<template>\n  <div :class=\"dynamicClass\">Content</div>\n</template>\n```\n\nGenerated code:\n```js\n// patchFlag = 2 (CLASS)\ncreateVNode(\"div\", { class: dynamicClass }, \"Content\", 2 /* CLASS */)\n```\n\n### Multiple Dynamic Properties\n\n```vue\n<template>\n  <div :class=\"cls\" :style=\"styles\">{{ text }}</div>\n</template>\n```\n\nGenerated code:\n```js\n// patchFlag = 7 (TEXT | CLASS | STYLE)\ncreateVNode(\"div\",\n  { class: cls, style: styles },\n  toDisplayString(text),\n  7 /* TEXT, CLASS, STYLE */\n)\n```\n\n### Dynamic Props\n\n```vue\n<template>\n  <input :value=\"inputValue\" :disabled=\"isDisabled\">\n</template>\n```\n\nGenerated code:\n```js\n// patchFlag = 8 (PROPS)\n// dynamicProps explicitly specifies props that may change\ncreateVNode(\"input\",\n  { value: inputValue, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]\n)\n```\n\n## Usage in Runtime\n\n### Optimization in patchElement\n\n```ts\nfunction patchElement(n1: VNode, n2: VNode) {\n  const el = n2.el = n1.el;\n  const { patchFlag, dynamicProps } = n2;\n\n  if (patchFlag > 0) {\n    // Optimized path: update only necessary parts based on flags\n\n    if (patchFlag & PatchFlags.CLASS) {\n      // Update only class\n      if (n1.props?.class !== n2.props?.class) {\n        hostSetClass(el, n2.props?.class);\n      }\n    }\n\n    if (patchFlag & PatchFlags.STYLE) {\n      // Update only style\n      hostPatchStyle(el, n1.props?.style, n2.props?.style);\n    }\n\n    if (patchFlag & PatchFlags.PROPS) {\n      // Update only specified props\n      for (const key of dynamicProps!) {\n        const prev = n1.props?.[key];\n        const next = n2.props?.[key];\n        if (prev !== next) {\n          hostPatchProp(el, key, prev, next);\n        }\n      }\n    }\n\n    if (patchFlag & PatchFlags.TEXT) {\n      // Update only text content\n      if (n1.children !== n2.children) {\n        hostSetElementText(el, n2.children as string);\n      }\n    }\n  } else if (patchFlag === PatchFlags.FULL_PROPS) {\n    // Check all props\n    patchProps(el, n1.props, n2.props);\n  } else {\n    // No flags: full diff\n    patchProps(el, n1.props, n2.props);\n    patchChildren(n1, n2, el);\n  }\n}\n```\n\n### Fragment Optimization\n\n```ts\nfunction patchFragment(n1: VNode, n2: VNode) {\n  const { patchFlag } = n2;\n\n  if (patchFlag & PatchFlags.STABLE_FRAGMENT) {\n    // Children order doesn't change: simple update\n    patchBlockChildren(n1.children, n2.children);\n  } else if (patchFlag & PatchFlags.KEYED_FRAGMENT) {\n    // Keyed children: key-based diff\n    patchKeyedChildren(n1.children, n2.children);\n  } else {\n    // Unkeyed: full diff\n    patchUnkeyedChildren(n1.children, n2.children);\n  }\n}\n```\n\n## Special Flags\n\n### CACHED (-1)\n\nIndicates that a static VNode is cached.\n\n```js\nconst _hoisted_1 = createVNode(\"div\", null, \"Static\", -1 /* CACHED */);\n```\n\nCached VNodes can skip diffing.\n\n### BAIL (-2)\n\nA hint to exit optimized mode. Used when the compiler's optimizations cannot be applied, such as when the user is using hand-written render functions.\n\n## dynamicProps\n\nThe `dynamicProps` array, used together with `patchFlag`, explicitly indicates which props are dynamic.\n\n```ts\n// Dynamic props are value and disabled\ncreateVNode(\"input\",\n  { type: \"text\", value: val, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]  // dynamicProps\n)\n```\n\nThis allows skipping comparison for `type` since it's static, and only checking `value` and `disabled`.\n\n## Integration with Block Tree\n\nPatch Flags work in conjunction with Block Tree optimization. Blocks have a `dynamicChildren` array that tracks only dynamic child nodes.\n\n```ts\nconst block = openBlock();\nconst vnode = createBlock(\"div\", null, [\n  createVNode(\"p\", null, \"static\"),  // not included in dynamicChildren\n  createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // included\n]);\n// block.dynamicChildren = [only the dynamic p]\n```\n\nWhen updating a Block, only `dynamicChildren` needs to be traversed, allowing comparison of static child nodes to be skipped.\n\n## Effect of Optimization\n\n### Before (without flags)\n```\nCompare all props: O(n)\nCompare all children: O(m)\nTotal: O(n + m)\n```\n\n### After (with flags)\n```\nCompare only dynamic props: O(k) where k << n\nCompare only dynamic children: O(l) where l << m\nTotal: O(k + l)\n```\n\nWhen most of the template is static, this optimization has a significant effect.\n\n## Summary\n\nThe Patch Flags implementation consists of the following elements:\n\n1. **Bit flags**: Efficiently represent multiple dynamic elements\n2. **Compiler integration**: Automatically generated during template analysis\n3. **Runtime optimization**: Skip unnecessary comparisons based on flags\n4. **dynamicProps**: Explicitly track dynamic props\n5. **Block Tree integration**: Efficiently update only dynamic child nodes\n\nPatch Flags are an important optimization technique that significantly improves Vue 3's Virtual DOM performance. By having the compiler and runtime work together, the advantages of template-based frameworks are maximized.\n\n<KawaikoNote variant=\"surprise\" title=\"Patch Flags complete!\">\n\nThis technology was born from the idea \"If we can analyze templates, we can also provide optimization hints.\"\nPlease experience the strength of template compilers that JSX doesn't have!\n\n</KawaikoNote>\n\nSource code up to this point:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/050_patch_flags)\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/040-optimizations/030-tree-flattening.md",
    "content": "# Tree Flattening (Block Tree)\n\n## What is Tree Flattening?\n\nTree Flattening (Block Tree) is an advanced optimization technique introduced in Vue 3. It \"flattens\" and collects dynamic nodes within templates, allowing direct updates to only dynamic nodes instead of traversing the entire tree during updates.\n\n<KawaikoNote variant=\"question\" title=\"Why 'flattening'?\">\n\nTraditional Virtual DOM required recursive traversal of the entire tree during updates.\nTree Flattening \"flattens\" only dynamic nodes into an array,\nallowing direct access while ignoring nested structures.\n\n</KawaikoNote>\n\n## Problems with Traditional Diff Algorithms\n\n### Template Example\n\n```vue\n<template>\n  <div>\n    <header>\n      <h1>Static Title</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ dynamicText }}</p>  <!-- The only dynamic part -->\n    </main>\n    <footer>\n      <p>Copyright 2024</p>\n    </footer>\n  </div>\n</template>\n```\n\n### Traditional Approach\n\n```\nTraverse entire tree:\ndiv\n├── header (static)\n│   ├── h1 (static)\n│   └── nav (static)\n│       ├── a (static)\n│       └── a (static)\n├── main\n│   └── p (dynamic) ← Only this actually needs updating\n└── footer (static)\n    └── p (static)\n\n→ Traverse 9 nodes to update 1\n```\n\n### Tree Flattening Approach\n\n```\nCollect only dynamic nodes:\ndynamicChildren = [p]\n\n→ Directly update 1 node\n```\n\n<KawaikoNote variant=\"funny\" title=\"Dramatic efficiency improvement!\">\n\nIf only 10 out of 1000 nodes are dynamic:\nTraditional approach requires 1000 comparisons,\nbut Tree Flattening needs only 10 comparisons.\n\n</KawaikoNote>\n\n## The Block Concept\n\n### What is a Block?\n\nA Block is \"a VNode subtree with stable structure.\" Within a Block, the following is guaranteed:\n\n1. The number of child nodes doesn't change\n2. The order of child nodes doesn't change\n3. No structural directives (`v-if`, `v-for`)\n\n### Elements that Create Blocks\n\nThe following elements create new Blocks:\n\n- Root element\n- Each branch of `v-if`\n- Each item of `v-for`\n- Components\n\n```vue\n<template>\n  <!-- Block 1: Root -->\n  <div>\n    <p>{{ text1 }}</p>\n\n    <!-- Block 2: v-if -->\n    <div v-if=\"show\">\n      <p>{{ text2 }}</p>\n    </div>\n\n    <!-- Block 3, 4, ...: Each v-for item -->\n    <div v-for=\"item in items\" :key=\"item.id\">\n      <p>{{ item.text }}</p>\n    </div>\n  </div>\n</template>\n```\n\n## VNode Extension\n\n### dynamicChildren\n\nAdd a `dynamicChildren` property to VNode to collect dynamic child nodes.\n\n```ts\nexport interface VNode {\n  // ... existing properties\n\n  /**\n   * List of dynamic child nodes within the Block\n   * Only these need to be traversed during updates\n   */\n  dynamicChildren: VNode[] | null;\n\n  /**\n   * Optimization hints for patch processing\n   */\n  patchFlag: number;\n\n  /**\n   * List of dynamic property names\n   */\n  dynamicProps: string[] | null;\n}\n```\n\n## openBlock and createBlock\n\n### Block Tracking\n\nBlock creation is done with the pair of `openBlock` and `createBlock`.\n\n```ts\n// Currently tracking Block\nlet currentBlock: VNode[] | null = null;\n\nexport function openBlock(): void {\n  currentBlock = [];\n}\n\nexport function createBlock(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode = createVNode(type, props, children, patchFlag, dynamicProps);\n\n  // Set collected dynamic nodes\n  vnode.dynamicChildren = currentBlock;\n  currentBlock = null;\n\n  return vnode;\n}\n```\n\n### Collecting Dynamic Nodes\n\nWithin `createVNode`, VNodes with patchFlag are added to currentBlock.\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children,\n    patchFlag: patchFlag || 0,\n    dynamicProps: dynamicProps || null,\n    dynamicChildren: null,\n    // ...\n  };\n\n  // Has patchFlag = dynamic node\n  // Add to currentBlock if it exists\n  if (patchFlag !== undefined && patchFlag > 0 && currentBlock) {\n    currentBlock.push(vnode);\n  }\n\n  return vnode;\n}\n```\n\n## Generated Code\n\n### Template\n\n```vue\n<template>\n  <div>\n    <h1>Static Title</h1>\n    <p>{{ message }}</p>\n    <span :class=\"cls\">{{ text }}</span>\n  </div>\n</template>\n```\n\n### Generated Render Function\n\n```js\nimport { openBlock, createBlock, createVNode, toDisplayString } from 'vue'\n\n// Static nodes are hoisted outside\nconst _hoisted_1 = createVNode(\"h1\", null, \"Static Title\")\n\nfunction render(_ctx) {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // Static (not included in dynamicChildren)\n      createVNode(\"p\", null, toDisplayString(_ctx.message), 1 /* TEXT */),\n      createVNode(\"span\", { class: _ctx.cls }, toDisplayString(_ctx.text), 3 /* TEXT | CLASS */)\n    ])\n  )\n}\n\n// Resulting VNode:\n// {\n//   type: \"div\",\n//   children: [_hoisted_1, p, span],\n//   dynamicChildren: [p, span]  // Only dynamic nodes\n// }\n```\n\n## patchBlockChildren Implementation\n\nWhen updating a Block, only traverse `dynamicChildren`.\n\n```ts\nfunction patchBlockChildren(\n  oldChildren: VNode[],\n  newChildren: VNode[],\n  container: RendererElement,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  for (let i = 0; i < newChildren.length; i++) {\n    const oldVNode = oldChildren[i];\n    const newVNode = newChildren[i];\n\n    // Patch only dynamic nodes\n    patch(oldVNode, newVNode, container, null, parentComponent);\n  }\n}\n```\n\n### Usage in patchElement\n\n```ts\nfunction patchElement(\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  const el = (n2.el = n1.el!);\n  const oldProps = n1.props || {};\n  const newProps = n2.props || {};\n\n  // Optimized patch using patchFlag\n  const { patchFlag, dynamicChildren } = n2;\n\n  if (patchFlag > 0) {\n    // Update only specific properties based on patchFlag\n    if (patchFlag & PatchFlags.CLASS) {\n      patchClass(el, newProps.class);\n    }\n    if (patchFlag & PatchFlags.STYLE) {\n      patchStyle(el, oldProps.style, newProps.style);\n    }\n    if (patchFlag & PatchFlags.TEXT) {\n      if (n1.children !== n2.children) {\n        el.textContent = n2.children as string;\n      }\n    }\n    // ...\n  }\n\n  // If dynamicChildren exists, use optimized path\n  if (dynamicChildren) {\n    patchBlockChildren(\n      n1.dynamicChildren!,\n      dynamicChildren,\n      el,\n      parentComponent\n    );\n  } else {\n    // Fallback: normal child patching\n    patchChildren(n1, n2, el, parentComponent);\n  }\n}\n```\n\n## Optimization Effect\n\nConsider the case of updating only 1 item out of 1000 list items:\n\n- **Full diff**: Traverses 1000+ nodes\n- **Patch Flags only**: Traverses 1000 nodes (property comparison is optimized)\n- **Tree Flattening**: Traverses only dynamic nodes (1 node)\n\nThe fewer dynamic nodes there are, the greater the effect of Tree Flattening.\n\n## Cases Where Blocks Break\n\nIn the following cases, Block optimization is disabled (BAIL mode):\n\n1. **Structural directives**: `v-if`, `v-for` create new Blocks\n2. **Dynamic components**: `<component :is=\"...\">`\n3. **Slot outlets**: `<slot />`\n\n```vue\n<template>\n  <div>\n    <!-- Block is split here -->\n    <div v-if=\"show\">\n      <p>{{ a }}</p>  <!-- Block A's dynamicChildren -->\n    </div>\n    <div v-else>\n      <p>{{ b }}</p>  <!-- Block B's dynamicChildren -->\n    </div>\n  </div>\n</template>\n```\n\n## Integration with Static Hoisting\n\nTree Flattening achieves maximum effect when combined with Static Hoisting.\n\n```ts\n// Static nodes are hoisted and not included in dynamicChildren\nconst _hoisted_1 = createVNode(\"header\", null, [\n  createVNode(\"h1\", null, \"Title\"),\n  createVNode(\"nav\", null, [/* ... */])\n]);\n\nfunction render() {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // Static: skipped\n      createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // Dynamic: tracked\n    ])\n  )\n}\n```\n\n1. **Static Hoisting**: Hoist static nodes outside the function (skip VNode generation)\n2. **Tree Flattening**: Collect only dynamic nodes (limit diff targets)\n3. **Patch Flags**: Update only dynamic properties (optimize property comparison)\n\n## Processing Flow\n\n```\n[Compile time]\nTemplate parsing\n  ↓\nDetect static nodes → Static Hoisting\n  ↓\nDetect dynamic nodes → Add Patch Flags\n  ↓\nIdentify Block boundaries → Insert openBlock/createBlock\n\n[Runtime]\nopenBlock() → currentBlock = []\n  ↓\ncreateVNode (static) → Don't add to currentBlock\n  ↓\ncreateVNode (dynamic) → currentBlock.push(vnode)\n  ↓\ncreateBlock() → vnode.dynamicChildren = currentBlock\n\n[Update time]\npatchElement(n1, n2)\n  ↓\nDoes n2.dynamicChildren exist?\n  ↓ Yes\npatchBlockChildren(n1.dynamicChildren, n2.dynamicChildren)\n  ↓\nPatch only dynamic nodes\n```\n\n## Summary\n\nThe Tree Flattening (Block Tree) implementation consists of:\n\n1. **dynamicChildren**: Array to collect dynamic child nodes\n2. **openBlock / createBlock**: Block creation and tracking\n3. **patchBlockChildren**: Patch only dynamic nodes\n4. **Block boundary management**: Create new Blocks with `v-if`, `v-for`, etc.\n\nThis optimization enables Vue 3 to achieve fast updates even in large-scale applications. When combined with Static Hoisting and Patch Flags, it enables optimizations unique to template-based frameworks.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/050-vapor/010-introduction.md",
    "content": "# Vapor Mode\n\n## What is Vapor Mode?\n\nVapor Mode is a new compilation strategy for Vue.js that improves performance by performing direct DOM operations without using the virtual DOM.\n\nIn traditional Vue.js, when a component's state changes, the virtual DOM is regenerated, diffing is performed, and the actual DOM is updated. In Vapor Mode, this virtual DOM overhead is eliminated, and only the necessary DOM operations are executed directly when reactive values change.\n\n## Detailed Resources\n\nFor detailed explanations of Vapor Mode, please refer to the following repository:\n\n**[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor)**\n\nThis repository provides in-depth explanations of Vue.js Vapor Mode's internal implementation.\n\n## Vapor Implementation in chibivue\n\nchibivue provides a minimal Vapor implementation in the `runtime-vapor` package.\nLet's look at a simple implementation to understand the basic concepts.\n\n### Basic Ideas\n\nThe core of Vapor Mode consists of two points:\n\n1. **Convert templates directly to DOM**: Generate actual DOM elements instead of virtual DOM nodes\n2. **Reflect reactive value changes directly to DOM**: Update only the changed parts without diffing\n\n### The template Function\n\nFirst, let's look at the `template` function that creates DOM elements from HTML strings:\n\n```ts\nexport type VaporNode = Element & { __is_vapor: true };\n\nexport const template = (tmp: string): VaporNode => {\n  const container = document.createElement(\"div\");\n  container.innerHTML = tmp;\n  const el = container.firstElementChild as VaporNode;\n  el.__is_vapor = true;\n  return el;\n};\n```\n\nThis function receives an HTML string and returns an actual DOM element. It directly manipulates the DOM without going through the virtual DOM.\n\n### The setText Function\n\nThe `setText` function updates text content:\n\n```ts\nexport const setText = (\n  target: Element,\n  format: string,\n  ...values: any[]\n): void => {\n  const fmt = (): string => {\n    let text = format;\n    for (let i = 0; i < values.length; i++) {\n      text = text.replace(\"{}\", values[i]);\n    }\n    return text;\n  };\n\n  if (!target) return;\n\n  if (!values.length) {\n    target.textContent = fmt();\n    return;\n  }\n\n  if (!format && values.length) {\n    target.textContent = values.join(\"\");\n    return;\n  }\n\n  target.textContent = fmt();\n};\n```\n\nThis function is called when reactive values change, directly updating the DOM's text content.\n\n### The on Function\n\nThe `on` function registers event listeners:\n\n```ts\nexport const on = (\n  element: Element,\n  event: string,\n  callback: () => void\n): void => {\n  element.addEventListener(event, callback);\n};\n```\n\n### Vapor Components\n\nComponents in Vapor Mode take a different form from regular Vue components:\n\n```ts\nexport type VaporComponent = (self: VaporComponentInternalInstance) => VaporNode;\n\nexport interface VaporComponentInternalInstance {\n  __is_vapor: true;\n  uid: number;\n  type: VaporComponent;\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null;\n  appContext: AppContext;\n  provides: Data;\n  isMounted: boolean;\n  // Lifecycle hooks\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  // ...\n}\n```\n\nA Vapor component is a function that receives an instance and returns a VaporNode (an actual DOM element).\n\n### Comparison of Compilation Results\n\nTraditional virtual DOM-based compilation result:\n\n```ts\n// Input: <div>{{ count }}</div>\n// Virtual DOM output\nfunction render(_ctx) {\n  return h(\"div\", null, _ctx.count);\n}\n```\n\nVapor Mode compilation result:\n\n```ts\n// Input: <div>{{ count }}</div>\n// Vapor output\nconst t0 = template(\"<div></div>\");\n\nfunction render(_ctx) {\n  const el = t0();\n  effect(() => {\n    setText(el, _ctx.count);\n  });\n  return el;\n}\n```\n\nIn Vapor Mode:\n- Templates are pre-generated as DOM elements (using the `template` function)\n- Reactive value updates directly manipulate the DOM within `effect`\n- There is no cost for virtual DOM generation and diffing\n\n## Summary\n\nVapor Mode is a new approach that improves performance by eliminating virtual DOM overhead. The `runtime-vapor` package in chibivue provides a minimal implementation of this concept.\n\nFor more detailed implementations and Vue.js's official Vapor Mode, please refer to [reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor).\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/050-vapor/020-vapor-compiler.md",
    "content": "# Vapor Compiler\n\nIn the previous section, we looked at the runtime functions that power Vapor Mode (`template`, `setText`, `on`).\nIn this section, let's implement a compiler that automatically generates code using these functions from templates.\n\n## Goal of the Vapor Compiler\n\nThe goal of the Vapor compiler is to transform a template like this:\n\n```html\n<button @click=\"count++\">{{ count }}</button>\n```\n\nInto code like this:\n\n```ts\nimport { template as _template, setText as _setText, on as _on, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  _renderEffect(() => {\n    _setText(_el0, \"\", count.value);\n  });\n  _on(_root, \"click\", () => count++);\n  return _root;\n})\n```\n\nThe key points are:\n\n1. **Static parts become template strings**: The HTML structure is created upfront as a string\n2. **Dynamic parts are handled by renderEffect**: Reactive value changes directly trigger DOM updates\n3. **Event handlers are directly attached**: No virtual DOM event delegation\n\n## Compiler Architecture\n\nThe Vapor compiler follows a pipeline similar to the regular template compiler, but uses a more sophisticated approach with an Intermediate Representation (IR):\n\n```\nTemplate (string)\n  ↓ [Parse]\nAST (Abstract Syntax Tree)\n  ↓ [Transform]\nIR (Intermediate Representation)\n  ↓ [Codegen]\nVapor Code (string)\n```\n\n## What is IR (Intermediate Representation)?\n\nIR (Intermediate Representation) is a data structure that sits between the AST and the final code.\nThe benefits of using IR include:\n\n1. **Separation of Concerns**: Clearly separates parsing from code generation\n2. **Ease of Optimization**: Static analysis and optimization are easier at the IR level\n3. **Extensibility**: Adding new features is straightforward\n\n### IR Structure\n\n```ts\n// Types of IR nodes\nenum IRNodeTypes {\n  ROOT = \"root\",\n  BLOCK = \"block\",\n  SET_TEXT = \"setText\",\n  SET_EVENT = \"setEvent\",\n  SET_PROP = \"setProp\",\n  IF = \"if\",\n  FOR = \"for\",\n}\n\n// Block IR Node - container for operations and effects\ninterface BlockIRNode {\n  type: IRNodeTypes.BLOCK;\n  node: RootNode | TemplateChildNode;\n  dynamic: IRDynamicInfo;\n  effect: IREffect[];      // Reactive operations\n  operation: OperationNode[]; // Non-reactive operations\n  returns: number[];       // Element IDs to return\n}\n\n// Effect - A set of reactive dependencies and operations\ninterface IREffect {\n  expressions: SimpleExpressionNode[]; // Dependent expressions\n  operations: OperationNode[];         // Operations to execute\n}\n```\n\n### Operation Node Examples\n\n```ts\n// Text update\ninterface SetTextIRNode {\n  type: IRNodeTypes.SET_TEXT;\n  element: number;  // Element ID\n  values: SimpleExpressionNode[];\n}\n\n// Event binding\ninterface SetEventIRNode {\n  type: IRNodeTypes.SET_EVENT;\n  element: number;\n  key: string;      // Event name\n  value: SimpleExpressionNode;\n  modifiers?: string[];\n}\n\n// Property binding\ninterface SetPropIRNode {\n  type: IRNodeTypes.SET_PROP;\n  element: number;\n  key: string;\n  value: SimpleExpressionNode;\n}\n```\n\n## The Role of the Transformer\n\nThe Transformer converts AST to IR.\nIt traverses each AST node and generates appropriate IR nodes.\n\n### TransformContext\n\n```ts\ninterface TransformContext {\n  root: RootIRNode;\n  block: BlockIRNode;\n  template: string;        // Static template string\n  elementCount: number;\n\n  // Assign element ID\n  reference(): number;\n\n  // Register reactive effects\n  registerEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void;\n\n  // Register non-reactive operations\n  registerOperation(...operations: OperationNode[]): void;\n\n  // Enter a new block (for v-if, v-for)\n  enterBlock(block: BlockIRNode): () => void;\n}\n```\n\n### Transform Flow\n\n```ts\nexport function transform(ast: RootNode, source: string): RootIRNode {\n  const ir = createRootIR(ast, source);\n  const context = createTransformContext(ir);\n\n  // Recursively transform children\n  transformChildren(ast.children, context);\n\n  // Store template string\n  ir.template.push(context.template);\n\n  return ir;\n}\n```\n\n### Directive Transformation\n\nEach directive is processed by a dedicated transform function:\n\n```ts\n// v-on transformation\nfunction transformVOn(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const eventName = (dir.arg as SimpleExpressionNode).content;\n\n  // Events are registered as operations (not reactive)\n  context.registerOperation({\n    type: IRNodeTypes.SET_EVENT,\n    element: elementId,\n    key: eventName,\n    value: dir.exp as SimpleExpressionNode,\n    modifiers: dir.modifiers,\n  });\n}\n\n// v-bind transformation\nfunction transformVBind(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const propName = (dir.arg as SimpleExpressionNode).content;\n\n  // Register as effect (reactive)\n  context.registerEffect([dir.exp as SimpleExpressionNode], [\n    {\n      type: IRNodeTypes.SET_PROP,\n      element: elementId,\n      key: propName,\n      value: dir.exp as SimpleExpressionNode,\n    },\n  ]);\n}\n```\n\n### Constant Expression Optimization\n\n`registerEffect` checks whether expressions are constant, and if so, registers them as regular `operation` instead of `effect`:\n\n```ts\nregisterEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void {\n  // Filter out constant expressions\n  const reactiveExpressions = expressions.filter((exp) => !isConstantExpression(exp));\n\n  // If no reactive dependencies, register as operation\n  if (reactiveExpressions.length === 0) {\n    context.registerOperation(...operations);\n    return;\n  }\n\n  // Register as effect\n  currentBlock.effect.push({\n    expressions: reactiveExpressions,\n    operations,\n  });\n}\n```\n\n## What is renderEffect?\n\n`renderEffect` is the core function of Vapor Mode.\nUnlike the Virtual DOM's diff-based approach, it directly tracks reactive dependencies and updates the DOM when they change.\n\n### How It Works\n\n```ts\n/**\n * renderEffect - Core mechanism for reactive DOM updates in Vapor Mode\n *\n * 1. Wraps a DOM update function in a reactive effect\n * 2. Automatically tracks which reactive values are accessed\n * 3. Re-runs the update function when tracked values change\n * 4. Updates only the specific DOM nodes that need changes\n *\n * Important: renderEffect also handles lifecycle hooks:\n * - Calls onBeforeUpdate hooks before each update (after initial mount)\n * - Calls onUpdated hooks after each update (after initial mount)\n */\nexport const renderEffect = (fn: () => void): void => {\n  const instance = currentInstance;\n\n  effect(() => {\n    // Before update: call onBeforeUpdate hooks (only after mount)\n    if (instance?.isMounted) {\n      const { bu } = instance;\n      if (bu) invokeArrayFns(bu);\n    }\n\n    // Execute the update\n    fn();\n\n    // After update: call onUpdated hooks (only after mount)\n    if (instance?.isMounted) {\n      const { u } = instance;\n      if (u) {\n        queueMicrotask(() => invokeArrayFns(u));\n      }\n    }\n  });\n};\n```\n\n### Generated Code Example\n\n```ts\n// Template: <span>{{ count }}</span>\n\nrenderEffect(() => {\n  setText(_el0, \"\", count.value)\n})\n\n// When count.value changes:\n// 1. onBeforeUpdate hooks are called\n// 2. The text content is updated\n// 3. onUpdated hooks are called (in a microtask)\n```\n\n### Comparison with Virtual DOM\n\n| Aspect | Virtual DOM | Vapor (renderEffect) |\n|--------|-------------|---------------------|\n| Update Granularity | Re-render entire component | Update only changed parts |\n| Tracking Method | Diff algorithm | Reactive dependency tracking |\n| Overhead | VNode creation and comparison | None (direct DOM operations) |\n\n## Codegen Implementation\n\nCodegen generates code from IR:\n\n```ts\nexport function generateVaporFromIR(ir: RootIRNode, options = {}): VaporCodegenResult {\n  const context = createVaporCodegenContext();\n\n  // Generate preamble (imports, etc.)\n  genVaporPreamble(context, options.isBrowser);\n\n  // Generate component function\n  push(`((_self) => {`);\n  indent();\n\n  // Generate template() call\n  push(`const _root = _template(\\`${ir.template[0]}\\`);`);\n\n  // Generate element references\n  for (let i = 0; i < elementCount; i++) {\n    push(`const _el${i} = _root${generateElementPath(i)};`);\n  }\n\n  // Generate non-reactive operations\n  for (const op of block.operation) {\n    genOperation(op, context);\n  }\n\n  // Generate reactive effects\n  for (const effect of block.effect) {\n    push(`_renderEffect(() => {`);\n    indent();\n    for (const op of effect.operations) {\n      genOperation(op, context);\n    }\n    deindent();\n    push(`});`);\n  }\n\n  push(`return _root;`);\n  deindent();\n  push(`})`);\n\n  return { code: context.code, preamble, ast: ir.node };\n}\n```\n\n## Usage Example\n\n```ts\nimport { compile } from \"@chibivue/compiler-vapor\";\n\n// IR-based compilation\nconst result = compile(`\n  <button @click=\"count++\" :class=\"btnClass\">Count: {{ count }}</button>\n`, { useIR: true });\n\nconsole.log(result.code);\n```\n\nOutput:\n\n```ts\nimport { template as _template, setText as _setText, on as _on, setClass as _setClass, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  const _el1 = _root;\n  _on(_el1, \"click\", count++);\n  _renderEffect(() => {\n    _setClass(_el1, btnClass);\n  });\n  _renderEffect(() => {\n    _setText(_el0, \"\", count);\n  });\n  return _root;\n})\n```\n\n## Summary\n\nThe Vapor compiler transforms templates to code through this pipeline:\n\n1. **Parse**: Transform template to AST\n2. **Transform**: Transform AST to IR (including optimizations)\n3. **Codegen**: Generate code from IR\n\nUsing IR enables:\n- Easier static analysis and optimization\n- Improved code maintainability\n- Simpler addition of new features\n\nWith `renderEffect`:\n- Fine-grained reactive updates are possible\n- Virtual DOM overhead is eliminated\n- Only changed parts are efficiently updated\n- Lifecycle hooks (onBeforeUpdate, onUpdated) are automatically handled\n\nIn the next section, we'll look at how SSR support allows us to render Vapor components on the server.\n"
  },
  {
    "path": "book/online-book/src/90-web-application-essentials/050-vapor/030-vapor-ssr.md",
    "content": "# Vapor SSR\n\nIn this section, we'll explore how to render Vapor components on the server side.\nSSR (Server-Side Rendering) for Vapor presents unique challenges since Vapor components directly manipulate the DOM, which doesn't exist on the server.\n\n## The Challenge\n\nVapor components work by:\n1. Creating DOM elements using `document.createElement` (via `template()`)\n2. Directly manipulating those elements with `textContent`, `addEventListener`, etc.\n\nOn the server, there's no `document` object. We need a different approach to generate HTML strings from Vapor components.\n\n## Solution Approach\n\nThere are two main approaches to Vapor SSR:\n\n1. **Mock DOM**: Create a fake DOM environment that captures operations and converts them to HTML\n2. **Reuse VNode SSR**: Use standard VNode-based SSR on the server, hydrate as Vapor on the client\n\nVue.js [PR #13226](https://github.com/vuejs/core/pull/13226) adopts the second approach. chibivue implements a similar approach.\n\n<KawaikoNote variant=\"base\" title=\"Vue.js Approach\">\nVue.js Vapor SSR uses the existing VNode-based SSR (compiler-ssr) on the server side, and uses `createVaporSSRApp` for hydration on the client side. This eliminates the need to create a separate SSR compiler.\n</KawaikoNote>\n\n## Implementation\n\n### Server-Side: Using VNode SSR\n\nIn Vapor SSR, Vapor components are compiled as regular VNode-based components on the server side. This allows `@chibivue/compiler-ssr` to be used directly.\n\n```ts\n// compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  ssr = false,\n  vapor = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  // Use compiler-ssr even in Vapor + SSR mode\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = defaultCompiler.compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n\n  // Add __vapor flag in Vapor + SSR mode\n  if (vapor && ssr) {\n    code = code.replace(\n      /export (function|const) ssrRender/,\n      \"export const __vapor = true;\\nexport $1 ssrRender\",\n    );\n  }\n\n  return { code, ast, source, preamble };\n}\n```\n\nThe `__vapor` flag indicates that Vapor mode should be used during hydration.\n\n### Client-Side: createVaporSSRApp\n\nOn the client side, `createVaporSSRApp` is used to hydrate SSR-rendered HTML.\n\n```ts\n// runtime-vapor/src/apiCreateVaporApp.ts\nexport function createVaporSSRApp(rootComponent: VaporComponent): VaporApp {\n  const context = createAppContext();\n\n  const app: VaporApp = {\n    // ... common app configuration ...\n\n    mount(containerOrSelector: Element | string) {\n      const container = typeof containerOrSelector === \"string\"\n        ? document.querySelector(containerOrSelector)\n        : containerOrSelector;\n\n      if (container?.hasChildNodes()) {\n        // Hydration mode when SSR content exists\n        const vnode = createVNode(rootComponent as any);\n        vnode.appContext = context;\n        const instance = hydrateVaporComponent(vnode, container, null);\n        app._instance = instance;\n      } else {\n        // Normal mount when no SSR content\n        // ...\n      }\n    },\n  };\n\n  return app;\n}\n```\n\n### Hydration\n\nThe hydration process reuses existing DOM elements while setting up reactivity and event listeners.\n\n```ts\n// runtime-vapor/src/hydration.ts\nexport function hydrateVaporComponent(\n  vnode: VNode,\n  container: Element,\n  parentInstance: VaporComponentInternalInstance | null = null,\n): VaporComponentInternalInstance {\n  const instance = createVaporComponentInstance(vnode, parentInstance);\n\n  // Set up hydration context\n  const ctx: VaporHydrationContext = {\n    node: container.firstChild,\n    parent: container,\n  };\n\n  setCurrentInstance(instance as any);\n  (instance as any).__hydrationCtx = ctx;\n\n  try {\n    const comp = instance.type as VaporComponent;\n    // Execute component - template() finds existing DOM\n    const el = comp(instance);\n\n    // Mark as mounted\n    instance.isMounted = true;\n\n    // Invoke mounted hooks\n    const { m } = instance as any;\n    if (m) invokeArrayFns(m);\n\n    return instance;\n  } finally {\n    unsetCurrentInstance();\n    delete (instance as any).__hydrationCtx;\n  }\n}\n```\n\n## Mock DOM Approach\n\nchibivue also implements the Mock DOM approach in `server-renderer`. This serves as a fallback when VNode SSR is not used.\n\n### SSR Elements\n\nWe create classes that mimic DOM elements but store data in memory:\n\n```ts\nclass SSRElement {\n  tagName: string;\n  attributes: Map<string, string> = new Map();\n  children: (SSRElement | SSRText)[] = [];\n  textContent: string = \"\";\n\n  constructor(tagName: string) {\n    this.tagName = tagName.toLowerCase();\n  }\n\n  setAttribute(name: string, value: string): void {\n    this.attributes.set(name, value);\n  }\n\n  addEventListener(): void {\n    // No-op in SSR - events are client-side only\n  }\n\n  appendChild(child: SSRElement | SSRText): void {\n    this.children.push(child);\n  }\n\n  toHTML(): string {\n    let html = `<${this.tagName}`;\n    for (const [name, value] of this.attributes) {\n      html += ` ${name}=\"${escapeHtml(value)}\"`;\n    }\n    html += \">\";\n\n    if (this.textContent) {\n      html += escapeHtml(this.textContent);\n    } else {\n      for (const child of this.children) {\n        html += child.toHTML();\n      }\n    }\n\n    html += `</${this.tagName}>`;\n    return html;\n  }\n}\n```\n\n## Usage Example\n\n### Server-Side\n\n```ts\nimport { createVNode } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport App from \"./App.vue\";\n\n// Render component to HTML string\nconst html = await renderToString(createVNode(App));\n\n// Send HTML response\nres.send(`\n<!DOCTYPE html>\n<html>\n  <head><title>My App</title></head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/entry-client.ts\"></script>\n  </body>\n</html>\n`);\n```\n\n### Client-Side\n\n```ts\n// entry-client.ts\nimport { createVaporSSRApp } from \"@chibivue/runtime-vapor\";\nimport App from \"./App.vue\";\n\n// Hydrate SSR-rendered HTML\ncreateVaporSSRApp(App).mount(\"#app\");\n```\n\n## Comparison with Virtual DOM SSR\n\n| Aspect | Virtual DOM SSR | Vapor SSR |\n|--------|-----------------|-----------|\n| Server Rendering | Traverses VNode tree, generates HTML | Same (uses VNode SSR) |\n| Client Hydration | Uses VNode diff | Directly references/manipulates DOM |\n| Bundle Size | Requires Virtual DOM runtime | Lightweight Vapor runtime |\n| Update Performance | Goes through diff algorithm | Direct DOM manipulation |\n\n## Architecture Benefits\n\nThe Vue.js-style Vapor SSR approach has the following benefits:\n\n1. **Code Reuse**: Existing `compiler-ssr` can be used directly\n2. **Consistent Output**: Server-generated HTML is identical to regular VNode SSR\n3. **Gradual Migration**: Can coexist with non-Vapor components\n4. **Maintainability**: No need to maintain a separate SSR compiler\n\n<KawaikoNote variant=\"warning\" title=\"Hydration Required\">\nThe server-rendered HTML is static. For interactivity, you need to hydrate the Vapor components on the client side, which will set up the reactive effects and event listeners.\n</KawaikoNote>\n\n## Limitations\n\nThe current implementation is minimal and has some limitations:\n\n1. **No streaming support**: The entire component is rendered before returning\n2. **No Suspense support**: Async component SSR support is limited\n3. **Hydration mismatch**: Warning functionality for client/server output differences is not implemented\n\n<KawaikoNote variant=\"base\" title=\"Future Improvements\">\nA more complete implementation would include:\n- Streaming SSR support\n- Hydration mismatch detection\n- Suspense integration\n</KawaikoNote>\n\n## Summary\n\nVapor SSR works as follows:\n\n1. **Server-Side**: Use `compiler-ssr` to generate HTML strings (same as VNode SSR)\n2. **Client-Side**: Use `createVaporSSRApp` for hydration\n3. **Hydration**: Reuse existing DOM elements while setting up reactivity\n\nThis approach allows Vapor components to benefit from SSR while gaining the performance benefits of direct DOM manipulation on the client side.\n"
  },
  {
    "path": "book/online-book/src/bonus/debug-vuejs-core.md",
    "content": "# Debugging the original source code\n\nThere may be times when you want to run and test the actual source code of Vue.js.  \nAs part of the approach in this book, we strongly recommend reading and understanding the original source code, as well as conducting source code reading and test plays.\n\nTherefore, we will introduce several methods for debugging the original source code that are not covered in the main text.\n\n(We will introduce them in an easy-to-understand order.)\n\n## Utilizing SFC Playground\n\nThis is the easiest method. It is widely known and even linked from the official documentation.\n\nhttps://play.vuejs.org\n\nIn this playground, you can not only write Vue components and check their behavior, but also check the compilation results of SFC.  \nIt is convenient because you can quickly check it in the browser. (Of course, you can also share it.)\n\n<video src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/8281e589-fdaf-4206-854e-25a66dfaac05\" controls />\n\n## Utilizing vuejs/core tests\n\nNext, let's try running the tests of [vuejs/core](https://github.com/vuejs/core).\nNaturally, you need to clone the source code of [vuejs/core](https://github.com/vuejs/core).\n\n```bash\ngit clone https://github.com/vuejs/core.git vuejs-core\n# NOTE: It is recommended to make it easy to understand since the repository name is `core`\n```\n\nThen,\n\n```bash\ncd vuejs-core\nni\npnpm test\n```\n\nYou can run the tests, so feel free to modify the source code you are interested in and run the tests.\n\nThere are several test commands other than `test`, so if you are interested, please check `package.json`.\n\nYou can read and understand the test code, modify the code and run the tests, or add test cases. There are various ways to use it.\n\n<img width=\"590\" alt=\"Screenshot 2024-01-07 0 31 29\" src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/3c862bd5-1d94-4d2a-a9fa-8755872098ed\">\n\n## Running the vuejs/core source code\n\nNext, this is the most convenient but still the method of actually modifying and running the vuejs/core source code.\n\nRegarding this, we have prepared projects that can be HMR with vite for both SFC and standalone, so please try using them.\nThis project is in the repository of [chibivue](https://github.com/chibivue-land/chibivue), so please clone it.\n\n```bash\ngit clone https://github.com/chibivue-land/chibivue.git\n```\n\nOnce cloned, run the script to create the project.\n\nAt this time, you should be asked for the **absolute path** of the local vuejs/core source code, so please enter it.\n\n```bash\ncd chibivue\nni\npnpm setup:vue\n\n# 💁 input your local vuejs/core absolute path:\n#   e.g. /Users/ubugeeei/oss/vuejs-core\n#   >\n```\n\nThis will create a Vue project in the chibivue repository that points to the local vuejs/core source code.\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/5d57c022-c411-4452-9e7e-c27623ec28b4\" controls/>\n\nThen, when you want to start, you can start it with the following command and check the operation while modifying the vuejs/core source code.\n\n```bash\npnpm dev:vue\n```\n\nOf course, HMR on the playground side,\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/a2ad46d8-4b07-4ac5-a887-f71507c619a6\" controls/>\n\nEven if you modify the vuejs/core code, HMR will work.\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/72f38910-19b8-4171-9ed7-74d1ba223bc8\" controls/>\n\n---\n\nAlso, if you want to check it in standalone, you can also use HMR by changing the index.html to load standalone-vue.js.\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/c57ab5c2-0e62-4971-b1b4-75670d3efeec\" controls/>\n"
  },
  {
    "path": "book/online-book/src/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl.md",
    "content": "# Hyper Ultimate Super Extreme Minimal Vue\n\n## Project Setup (0.5 min)\n\n```sh\n# Clone this repository and navigate to it.\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\n\n# Create a project using the setup command.\n# Specify the root path of the project as an argument.\npnpm setup ../my-chibivue-project\n```\n\nThe project setup is now complete.\n\nLet's now implement packages/index.ts.\n\n## createApp (1 min)\n\nFor the create app function, let's consider a signature that allows specifying the setup and render functions. From the user's perspective, it would be used like this:\n\n```ts\nconst app = createApp({\n  setup() {\n    // TODO:\n  },\n  render() {\n    // TODO:\n  },\n})\n\napp.mount('#app')\n```\n\nLet's implement it:\n\n```ts\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\n```\n\nWe can then return an object that implements the mount function:\n\n```ts\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    // TODO: patch rendering\n  },\n})\n```\n\nThat's it for this part.\n\n## h Function and Virtual DOM (0.5 min)\n\nTo perform patch rendering, we need a Virtual DOM and functions to generate it.\n\nThe Virtual DOM represents tag names, attributes, and child elements using JavaScript objects. The Vue renderer handles the Virtual DOM and applies updates to the actual DOM.\n\nLet's consider a VNode that represents a name, a click event handler, and child elements (text) for this example:\n\n```ts\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n```\n\nThat's it for this part.\n\n## patch rendering (2 min)\n\nNow let's implement the renderer.\n\nThis rendering process is often referred to as patching because it compares the old and new Virtual DOMs and applies the differences to the actual DOM.\n\nThe function signature would be:\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  // TODO:\n}\n```\n\nn1 represents the old VNode, n2 represents the new VNode, and container is the root of the actual DOM. In this example, `#app` would be the container (the element mounted with createApp).\n\nWe need to consider two types of operations:\n\n- Mount  \n  This is the initial rendering. If n1 is null, it means it's the first rendering, so we need to implement the mount process.\n- Patch  \n  This compares the VNodes and applies the differences to the actual DOM.  \n  This time, however, we only update children and do not detect differences.\n\nLet's implement it:\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n```\n\nThat's it for this part.\n\n## Reactivity System (2 min)\n\nNow let's implement the logic to track state changes defined in the setup option and trigger the render function. This process of tracking state changes and performing specific actions is called the \"Reactivity System.\"\n\nLet's consider using the `reactive` function to define states:\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n  // ..\n  // ..\n})\n```\n\nIn this case, when a state defined with the `reactive` function is modified, we want to trigger the patch process.\n\nIt can achieve this using a Proxy object. Proxies allow us to implement functionality for get/set operations. In this case, we can use the set operation to execute the patch process when a set operation occurs.\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      // ??? Here we want to execute the patch process\n      return res\n    },\n  })\n```\n\nThe question is, what should we trigger in the set operation? Normally, we would track the changes using the get operation, but in this case, we will define an `update` function in the global scope and refer to it.\n\nLet's use the previously implemented render function to create the update function:\n\n```ts\nlet update: (() => void) | null = null // We want to reference this with Proxy, so it needs to be in the global scope\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup() // Only run setup on the first rendering\n    update = () => {\n      // Generate a closure to compare prevVNode and VNode\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n```\n\nNow we just need to call it in the set operation of the Proxy:\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.() // Execute the update\n      return res\n    },\n  })\n```\n\nThat's it!\n\n## template compiler (5 min)\n\nSo far, we have been able to implement declarative UI by allowing users to use the render option and the h function. However, in reality, we want to write it in an HTML-like way.\n\nTherefore, let's implement a template compiler that converts HTML to the h function.\n\nThe goal is to convert a string like this:\n\n```\n<button @click=\"increment\">state: {{ state.count }}</button>\n```\n\nto a function like this:\n\n```\nh(\"button\", increment, \"state: \" + state.count)\n```\n\nLet's break it down a bit.\n\n- parse  \n  Parse the HTML string and convert it into an object called AST (Abstract Syntax Tree).\n- codegen  \n  Generate the desired code (string) based on the AST.\n\nNow, let's implement AST and parse.\n\n```ts\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\n```\n\nThe AST we are dealing with this time is as shown above. It is similar to VNode, but it is completely different and is used for generating code. Interpolation represents the mustache syntax. A string like <span v-pre>`{{ state.count }}`</span> is parsed into an object (AST) like <span v-pre>`{ content: \"state.count\" }`</span>.\n\nNext, let's implement the parse function that generates AST from the given string. For now, let's implement it quickly using regular expressions and some string operations.\n\n```ts\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\n```\n\nNext is codegen. Generate the invocation of the h function based on the AST.\n\n```ts\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\n```\n\nThe state is referenced from the argument `_ctx`.\n\nBy combining these, we can complete the compile function.\n\n```ts\nconst compile = (template: string): string => codegen(parse(template))\n```\n\nWell, actually, as it is, it only generates the invocation of the h function as a string, so it doesn't work yet.\n\nWe will implement it together with the sfc compiler.\n\nWith this, the template compiler is complete.\n\n## sfc compiler (vite-plugin) (4 min)\n\nLast! Let's implement a plugin for vite to support sfc.\n\nIn vite plugins, there is an option called transform, which allows you to transform the contents of a file.\n\nThe transform function returns something like `{ code: string }`, and the string is treated as source code. In other words, for example,\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: \"vite-plugin-chibivue\",\n  transform: (code: string, id: string) => ({\n    code: \"\";\n  }),\n});\n```\n\nwill make the content of all files an empty string. The original code can be received as the first argument, so by converting this value properly and returning it at the end, you can transform it.\n\nThere are 5 things to do.\n\n- Extract what is exported as default from the script.\n- Convert it into code that assigns it to a variable. (For convenience, let's call the variable A.)\n- Extract the HTML string from the template and convert it into a call to the h function using the compile function we created earlier. (For convenience, let's call the result B.)\n- Generate code like `Object.assign(A, { render: B })`.\n- Generate code that exports A as default.\n\nNow let's implement it.\n\n```ts\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\nAfter that, implement it in the plugin.\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : code, // Only for files with the .vue extension\n})\n```\n\n## The End\n\nYes. With this, we have successfully implemented until SFC.\nLet's take another look at the source code.\n\n```ts\n// create app api\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\nlet update: (() => void) | null = null\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup()\n    update = () => {\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n\n// Virtual DOM patch\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n\n// Virtual DOM\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n\n// Reactivity System\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.()\n      return res\n    },\n  })\n\n// template compiler\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\nconst compile = (template: string): string => codegen(parse(template))\n\n// sfc compiler (vite transformer)\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : null,\n})\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\nSurprisingly, we were able to implement it in about 110 lines. (Now no one will complain, phew...)\n\nPlease make sure to also try the main part of the main part!! (This is just an appendix, though)\n"
  },
  {
    "path": "book/online-book/src/bonus/hyper-ultimate-super-extreme-minimal-vue/index.md",
    "content": "# chibivue? Where is it chibi!? It's too big, I can't handle it!\n\n## It's big...\n\nTo those who thought so, I sincerely apologize.\n\nBefore picking up this book, you may have imagined something smaller.\n\nAllow me to make a little excuse, even I didn't intend to make it this big.\n\nAs I continued working on it, I found it enjoyable and thought, \"Oh, should I add this functionality next?\" And that's how it ended up like this.\n\n## Understood. Let's set a time limit.\n\nOne of the factors that caused it to become too big was that \"there was no time limit\".\n\nSo, in this appendix, I will try to implement it in \"**15 minutes**\".\n\nOf course, I will also limit the explanation to just one page.\n\nFurthermore, not only the page, but also the \"implementation itself will be contained in one file\" is the goal I will try to achieve.\n\nHowever, even if it's one file, it's meaningless to write 100,000 lines in one file, so I will aim to implement it in less than 150 lines.\n\n![Full source of Hyper Ultimate Super Extreme Minimal Vue in one file](/figures/bonus/hyper-ultimate-super-extreme-minimal-vue/full-source-screenshot.png)\n\nThe title is \"**Hyper Ultimate Super Extreme Minimal Vue**\".\n\n::: info About the name\n\nI think many people thought that the name is quite childish.\n\nI think so too.\n\nHowever, there is a proper reason for this name.\n\nWhile emphasizing that it is extremely small, I wanted an abbreviation, so it became this word order.\n\nThe abbreviation is \"HUSEM Vue (Balloon Vue)\".\n\n\"HU-SEN\" [fuːsen] means \"balloon\" in Japanese.\n\nAlthough I will be implementing it in a very sloppy way from now on, I am comparing that sloppiness to a \"balloon\" that will burst if even a needle touches it.\n\n:::\n\n## You're just going to implement a Reactivity System, right?\n\nNo, that's not the case. This time, I will try to list what will be implemented in 15 minutes.\n\n- create app api\n- Virtual DOM\n- patch rendering\n- Reactivity System\n- template compiler\n- sfc compiler (vite-plugin)\n\nI will be implementing these things.\n\nIn other words, SFC will work.\n\nAs for the source code, I assume that the following will work:\n\n```vue\n<script>\nimport { reactive } from 'hyper-ultimate-super-extreme-minimal-vue'\n\nexport default {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"increment\">state: {{ state.count }}</button>\n</template>\n```\n\n```ts\nimport { createApp } from 'hyper-ultimate-super-extreme-minimal-vue'\n\n// @ts-ignore\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n"
  },
  {
    "path": "book/online-book/src/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: \"chibivue\"\n  text: \"Step by Step, from just one line of \\\"Hello, World\\\".\"\n  tagline: powered by VitePress\n  image: /figures/_brand/logo.png\n  actions:\n    - theme: brand\n      text: Dive into book ->\n      link: /00-introduction/010-about\n    - theme: alt\n      text: Vue.js Official\n      link: https://vuejs.org/\n\nfeatures:\n  - title: Reactivity System\n    details: From the basic principles of the Reactivity System, we will cover a wide range of implementations, from EffectScope to advanced APIs like CustomRef.\n  - title: Virtual DOM\n    details: We will cover a broad range of implementations, from the basic setup of the Virtual DOM to patch rendering and scheduler implementations.\n  - title: Template Compiler\n    details: From the fundamental implementation of the template compiler, we will extend our coverage to data binding and directive implementations.\n  - title: Single File Component\n    details: Starting from the basic implementation of SFCs, we will delve into a wide range of areas, from script setup to compiler macros and scoped CSS implementations.\n---\n"
  },
  {
    "path": "book/online-book/src/ja/00-introduction/010-about.md",
    "content": "# はじめに\n\n## 本書の目的\n\nこの本を手に取って頂きありがとうございます！  \n少しでも興味を持って頂いたということで大変嬉しく思います．  \nこの本の目的について最初にまとめておきます．\n\n**☆ 目的**\n\n- **Vue.js についての理解を深める**  \n  Vue.js とは何なのか? どのような構成で成り立っているのか?\n- **Vue.js の基本的な機能を実装できるようになる**  \n  実際に基本的な機能を実装してみる\n- **vuejs/core のソースコードを読めるようになる**  \n  実装と本家のコードとの関連を把握して，実際にどんな実装になっているのかを把握する\n\nいくつかざっくりした目的を挙げましたが，この全てを満たす必要はないですし，完璧を目指す必要はありません．  \n通しで全て読んでもらっても，部分的に読みたいところを読んでもらってもご自由に．．  \n少しでも参考になる部分があれば嬉しいです！\n\n## 想定する対象者\n\n- **Vue.js を触ったことがある**\n- **TypeScript が書ける**\n\n以上があれば他の知識は何も必要ありません．  \nこの本の中では普段聞き慣れない言葉がたくさん出てくるかもしれませんが，可能な限り前程知識は排除し，随時説明しながらこの本内で完結できることを目指します．  \nですが，Vue.js や TypeScript についてまだ十分に扱うことができないという方であれば，まずはそちらの方から学ばれることを推奨します．\\\n(基本的な機能について知っていればそれで十分です! (詳しくある必要はない (かも)))\n\n## この本(著者)が意識していること (したいこと)\n\nこの本を書く上で意識しておきたいことをいくつかまとめておくので，その心構えで読んでいただけると幸いです．\nもしも，この点で欠けている点があればご指摘ください．\n\n- **前程知識の排除**  \n  前述の「想定する対象者」に関する説明と重複してしまいますが，この本では可能な限り前程知識は排除し，随時説明してこの本で完結できることを目指します．\\\n  これはより多くの方々にとってわかりやすい説明を広めたいからです．\\\n  ある程度，知識のある方にとっては冗長な説明に感じる部分が多くあるかもしれませんが，その辺はご了承ください.\n\n- **インクリメンタルな実装**  \n  この本の目的の一つとして，Vue.js を自分の手で小さく実装するというのがあります.\\\n  つまりはこの本は実装ベースで説明をしていくのですが，その際はなるべく小さくインクリメンタルな実装を心がけます．  \n  もう少し具体的にいうと，「動かない状態をなるべく減らす」ということです．  \n  最後まで完成しないと動かないなどといった実装は避け，なるべく常に成果物が動いている状態を目指します．  \n  これは筆者が個人的に何かを実装する上でかなり大切にしていることで，動かないコードを書き続けるのはやはり辛いです．  \n  不完全ではあるものの，常に動いているような状況を作ることで楽しくやっていきましょう．  \n  「やった! 次はここまで動くようになった！」というのを小さく繰り返していくようなイメージです！\n\n- **特定のフレームワーク・ライブラリ・言語などの優劣をつけるような内容にはしない**  \n  今回は Vue.js を主題として取り上げますが，昨今は他にも素晴らしいフレームワークやライブラリ・言語が多数あります．\\\n  実際，著者も Vue.js 以外でも好きなライブラリ等はたくさんありますし，自分では書かないけれどそれらで作られたサービス・知見にとても助けられることも日常茶飯事です．\\\n  本書の目的はあくまで，「Vue.js について理解する」であり，他の議論はその範囲を超えます．ついてはそれぞれの優劣をつけるような目的は含みません．\n\n## このオンラインブックで取り上げることと流れ\n\n本書はかなりボリューミーな感じになってしまっているので，各部門ごとに達成マイルストーンを立てて分割します．\n\n- **Minimal Example 部門**  \n  最小の構成で Vue.js を実装します．\\\n  機能としても一番小さい部門ですが， 仮想 DOM, リアクティビティシステム, テンプレートのコンパイラ，Single File Component のコンパイラの実装を行います．  \n  とはいえ実用的なものからは程遠く，かなり簡略化した実装になっています．  \n  しかし，Vue.js の全体像がどうなっているかのざっくりした理解をしたい方にとっては十分な達成率です．  \n  入門の部門でもあるということで，説明も他の部門と比べて最も丁寧に行なっています．  \n  この部門を終えてからは，ある程度 Vue.js 本家のソースコードが読めるようになっているかと思います．  \n  機能的には概ね以下のようなコードが動くようになります．\n\n  ```vue\n  <script>\n  import { reactive } from \"chibivue\";\n\n  export default {\n    setup() {\n      const state = reactive({ message: \"Hello, chibivue!\", input: \"\" });\n\n      const changeMessage = () => {\n        state.message += \"!\";\n      };\n\n      const handleInput = (e) => {\n        state.input = e.target?.value ?? \"\";\n      };\n\n      return { state, changeMessage, handleInput };\n    },\n  };\n  </script>\n\n  <template>\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\">click me!</button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n    </div>\n  </template>\n\n  <style>\n  .container {\n    height: 100vh;\n    padding: 16px;\n    background-color: #becdbe;\n    color: #2c3e50;\n  }\n  </style>\n  ```\n\n  ```ts\n  import { createApp } from \"chibivue\";\n  import App from \"./App.vue\";\n\n  const app = createApp(App);\n\n  app.mount(\"#app\");\n  ```\n\n- **Basic Virtual DOM 部門**  \n  ここではある程度実用的な仮想 DOM を用いたパッチレンダリング機能の実装を行います．\\\n  [Suspense](https://ja.vuejs.org/guide/built-ins/suspense) などの機能や最適化の実装は行いませんが，基本的なレンダリングであれば問題なくできる程度の完成度です．\\\n  スケジューラの実装などもここで行います．\n\n- **Basic Reactivity System 部門**  \n  Minimal Example 部門では reactive という API を実装しましたが，この部門ではその他の API を実装します．\\\n  ref/watch/computed というベーシックな API をはじめ，effectScope や shallow 系などの応用的な API まで幅広く実装します．\n\n- **Basic Component System 部門**  \n  ここではコンポーネント関する基本実装を行います．実は，Basic Virtual DOM 部門で Component System のベースは実装してしまうので，それ以外の部分の Component System を実装します．\\\n  例えば provide/inject， リアクティビティシステムの拡張，ライフサイクルフックなどです．\n\n- **Basic Template Compiler 部門**  \n  Basic Virtual DOM で実装した仮装 DOM システムに対応する機能のコンパイラに加え，v-on, v-bind, v-for 等のディレクティブなどの実装を行います．  \n  基本的にはコンポーネントの template オプションを利用した実装で，SFC の対応はここではやりません．\n\n- **Basic SFC Compiler 部門**  \n  ここでは Basic Template Compiler 部門で実装したテンプレートのコンパイラを活用しつつある程度実用的な SFC コンパイラを実装します．  \n  具体的には script setup やコンパイラマクロの実装を行います．  \n  ここまでくると触り心地としてはかなり普段の Vue に近づきます．\n\n- **Web Application Essentials 部門**  \n  Basic SFC Compiler 部門までで，ある程度実用的な Vue.js の機能が実装されます．  \n  しかし，Web アプリケーションを開発するにはまだまだ不十分です．例えばグローバルなステートの管理であったり，router の管理であったりが必要です．  \n  この部門ではそういった外部プラグインの実装を行って，「Web アプリケーションを開発する」という視点においてさらに実用的なものを目指します．  \n  一部，Vue.js が行っている最適化の実装なども行います．\n\n## この本に対する意見や質問について\n\nこの本に関する質問や意見については可能な限り対応しようと思っています．  \nTwitter で声をかけてもらってもいいですし (DM でも TL でも)，リポジトリを公開しているのでそちらの issue 等で投げてもらっても，PR を出していただいても問題ないです．  \nこの本も自分自身の理解も完璧ではないと思っているので，随時ご指摘いただけると嬉しいのと，「この説明がわかりづらい！」などもあれば是非問い合わせて欲しいです．  \n少しでも多くの方にわかりやすく，正しい説明を広めたいので，ぜひみなさんと一緒に作り上げていけたらなと思います．\n\nX: https://x.com/ubugeeei\n\n## Discord Server について\n\nこの本の Discord サーバーを作りました！ (2024/01/01)  \n~~ここではこのオンラインブックに関するアナウンスや質問対応・Tips の共有などを行っています．~~\\\nその他雑談なども大歓迎なので，ぜひ chibivue ユーザー同士で楽しくコミュニケーションしましょう．  \n日本語話者が多く会話も今の所は日本語が多いですが，日本語話者以外の方も大歓迎なのであまり気にせず参加してください！ (母語を使ってもらって全く問題ないです)\n\n最近は，chibivue にとどまらず，Vue.js の日本のコミュニティサーバーとして活発に活動しています！\n\n### ざっくりやっていること\n\n- 自己紹介 (任意)\n- chibivue に関するアナウンス (更新情報など)\n- Tips の共有\n- 質問対応\n- 要望対応\n- 雑談\n\n### どこから参加できるか\n\n招待リンクはこちらです: https://discord.gg/aVHvmbmSRy\n\nこの本のヘッダー右上の Discord ボタンからも参加できます．\n\n## 著者について\n\n**ubugeeei (もののけ王)**\n\n<img class=\"author-avatar\" src=\"/figures/_people/ubugeeei-avatar.jpg\" alt=\"ubugeeei\" width=\"160\" height=\"160\">\n\n[Vue.js](https://vuejs.org/about/team.html) Core Team, [Vue.js Japan User Group](https://github.com/vuejs-jp) Core Staff, [Vite+](https://github.com/voidzero-dev/vite-plus) Core Contributor, [株式会社メイツ](https://github.com/mates-inc) Chief Engineer．\\\n[chibivue land](https://github.com/chibivue-land) King. https://chibivue.land\n\nVue と言語処理系，開発体験のためのツールや本を作っています．主なものは [chibivue](https://github.com/chibivue-land/chibivue), [Vize](https://github.com/ubugeeei/vize), [Ox Content](https://github.com/ubugeeei/ox-content), [reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor), [Vapor Moon](https://github.com/ubugeeei/vapor-moon) です．\n\nhttps://wtrclred.io/\n\nもしよろしければ，スポンサーとして応援していただけると嬉しいです！ https://github.com/sponsors/ubugeeei\n\n## スポンサー\n\n<div class=\"sponsors-block\">\n<a class=\"sponsors-image-link\" href=\"https://github.com/sponsors/ubugeeei\">\n  <img class=\"sponsors-image sponsors-image--light\" src=\"/figures/_sponsors/ubugeeei-sponsors.png\" alt=\"ubugeeei's sponsors\" />\n  <img class=\"sponsors-image sponsors-image--dark\" src=\"/figures/_sponsors/ubugeeei-sponsors-dark.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n<p>もしよろしければ，私の仕事を応援していただけると嬉しいです！</p>\n<p><a href=\"https://github.com/sponsors/ubugeeei\">https://github.com/sponsors/ubugeeei</a></p>\n\n</div>\n"
  },
  {
    "path": "book/online-book/src/ja/00-introduction/020-what-is-vue.md",
    "content": "# Vue.jsとは\n\n## Vue.js についてのおさらい\n\n早速本題に入っていきましょう．  \nっとその前に改めて Vue.js についておさらいしておきます．\n\n## Vue.js ってなんだっけ？\n\nVue.js とは「Web ユーザーインタフェース構築のための，親しみやすく，パフォーマンスと汎用性の高いフレームワーク」です．  \nこれは[公式ドキュメントのトップページ](https://ja.vuejs.org/)に掲げられているものです．  \nここに関しては僕の解釈を入れるよりも公式の言葉をそのまま持ってくるのがわかりやすいと思うので以下に引用します．\n\n> Vue (発音は /vjuː/、view と同様) は、ユーザーインターフェースの構築のための JavaScript フレームワークです。標準的な HTML、CSS、JavaScript を土台とする、コンポーネントベースの宣言的なプログラミングモデルを提供します。シンプルなものから複雑なものまで、ユーザーインターフェースの開発を効率的に支えるフレームワークです。\n\n> 宣言的レンダリング: Vue では、標準的な HTML を拡張したテンプレート構文を使って、HTML の出力を宣言的に記述することができます。この出力は、JavaScript の状態に基づきます。\n\n> リアクティビティ: Vue は JavaScript の状態の変化を自動的に追跡し、変化が起きると効率的に DOM を更新します。\n\n> 最小限のサンプルは、次のようになります:\n>\n> ```ts\n> import { createApp } from \"vue\";\n>\n> createApp({\n>   data() {\n>     return {\n>       count: 0,\n>     };\n>   },\n> }).mount(\"#app\");\n> ```\n>\n> ```html\n> <div id=\"app\">\n>   <button @click=\"count++\">Count is: {{ count }}</button>\n> </div>\n> ```\n\n[引用元](https://ja.vuejs.org/guide/introduction.html#what-is-vue)\n\n宣言的レンダリングやリアクティビティに関してはそれを説明するチャプターで詳しくやるので，ここではほんとに概要レベルの理解で問題ないです．\n\n![Vue.js as an implementation map](/figures/00-introduction/what-is-vue/vue-implementation-map.svg)\n\nまた，ここで「フレームワーク」という言葉が出てきていますが，Vue.js は「プログレッシブフレームワーク」を謳っています．\nそれについてもドキュメントの以下の部分を参照するのが最も端的で正確でわかりやすいと思います．\n\nhttps://ja.vuejs.org/guide/introduction.html#the-progressive-framework\n\n## 公式ドキュメントとこの本の違い\n\n公式ドキュメントの方では，この次は「どうやって Vue.js を使うか」という点についてフォーカスし，チュートリアルやガイドが豊富に展開されています．\n\nしかし，この本では少し切り口を変えて「Vue.js はどうやって実装されているか」という点についてフォーカスし，実際にコードを書きながら小さな Vue.js を作っていきます．\n\nまた，この本は公式のものではなく完全なものではありません．もしかするとおかしな点がいくつか残っていることもあるとおもうので，随時ご指摘いただければと思います．\n"
  },
  {
    "path": "book/online-book/src/ja/00-introduction/030-vue-core-components.md",
    "content": "# Vue.js を構成する主要な要素\n\n## Vue.js のリポジトリ\n\nVue.js はこのリポジトリにあります．  \nhttps://github.com/vuejs/core\n\n実はこれは 3.x 系のリポジトリで，2.x 以前はまた別のリポジトリにあります．  \nhttps://github.com/vuejs/vue\n\n前提として，今回は core リポジトリ(3.x 系)を使って説明していきます．\n\n## Vue.js を構成する主要な要素\n\nさて，まずは Vue.js の実装の全体像から把握してみましょう．\nVue.js のリポジトリにコントリビュートに関するマークダウンファイルがあるので，余力がある方はそこをみてみると構成についても色々書いてます．(まあみなくてもいいです)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md\n\nざっくり説明すると，Vue.js は以下のような主要な要素があります．\n\n## ランタイム\n\nランタイムとは実際の動作に影響する部分全般です．レンダリングであったり，コンポーネントのステート管理であったり．  \nVue.js で開発した Web アプリケーションのうち，ブラウザ上やサーバー上(SSR の場合)で動作している部分全般のことです．  \n具体的には以下の要素を含んでます．(それぞれの詳しい説明は各チャプターでやるのでざっくり)\n\n### コンポーネントシステム\n\nVue.js はコンポーネント指向なフレームワークです．\\\nそれぞれのユーザーの要件に応じて保守性の高いコンポーネントを作ってカプセル化・再利用を行うことができます．\\\nまた，コンポーネント間でのステートの共有(props/emits や provide/inject など)であったり，ライフサイクルフックの提供を行ったりしています．\n\n### リアクティビティシステム\n\n「リアクティビティ」は日本語で言うと「反応性」です．\nコンポーネントでもつステートを追跡し，変更があった場合に画面の更新をしたりします．\nこの，追跡して反応して何かを行うことを反応性と呼んでいます．\n\n```ts\nimport { ref } from \"vue\";\n\nconst count = ref(0);\n\n// この関数が実行されるとcountを表示していた画面も更新される\nconst increment = () => {\n  count.value++;\n};\n```\n\n(よくよく考えると，値を変更しているだけなのにちゃんと画面が更新されているのは不思議ですよね．)\n\n### 仮想 DOM\n\n仮想 DOM もまた，Vue.js の強力なシステムの一つです．\\\n仮想 DOM は JS のランタイム上に DOM を模倣した JavaScript のオブジェクトを定義し，それを現在の DOM と見立て，更新時は現在の仮想 DOM と新しい仮想 DOM とを比較して差分のみを本物の DOM に反映する仕組みです．詳しくは専用のチャプターで詳しく解説します．\n\n\n## コンパイラ\n\nコンパイラとは開発者インタフェースと内部実装の変換を担う部分です．  \nここでいう「開発者インタフェース」とは，「実際に Vue.js を使用して Web アプリケーション開発を行う開発者」と「Vue の内部実装」の境界のことです．  \n具体的には，普段皆さんが書いている Vue を使った実装を想像してもらって，それを内部で扱うための方に変換をする機能が Vue.js に存在していると思ってもらえれば大丈夫です．\n\nVue.js で開発をしていると，明らかに JavaScript の記述ではない部分があると思います．テンプレートのディレクティブであったり，Single File Component であったり．  \nこれらの記法(文法)は Vue.js が提供しているもので，これを JavaScript のみ記述に変換する機能があるのです．\nそしてこの機能はあくまで開発段階で使われるもので Web アプリケーションとして実際の動作にしている部分ではありません．(JavaScript コードにコンパイルする役目のみを果たします．)\n\nこのコンパイラも大きく二つのセクションに別れています．\n\n### テンプレートのコンパイラ\n\n名前の通りテンプレート部分のコンパイラです．\n具体的には v-if や v-on といったディレクティブの記法や，ユーザーコンポーネントの記述 (`<Counter />` のようにオリジナルのタグとして書くやつ) や slot の機能などです．\n\n### Single File Component のコンパイラ\n\n名前の通り Single File Component のコンパイラです．\n.vue という拡張子のファイルで，コンポーネントの template, script, style を単一のファイルで記述する機能です．\\\nscript setup で使用する [defineProps や defineEmits](https://ja.vuejs.org/api/sfc-script-setup#defineprops-defineemits) などもこのコンパイラが提供しています．(これは後述)\n\nそして，この SFC コンパイラですが，実際には Webpack や Vite などのツールと組み合わされて使用されます．\n他のツールのプラグインとしての実装部分ですが，ここは core リポジトリには存在していません．core に存在するのは SFC コンパイラの主要な機能で，プラグインはプラグインで別のリポジトリに実装されています．(参考: [vitejs/vite-plugin-vue](https://github.com/vitejs/vite-plugin-vue))\n\nちなみに今回の実装では実際に Vite のプラグインを実装して自作 SFC コンパイラを動作させます．\n\n## vuejs/core のディレクトリを覗いてみる\n\nVue の主要な要素をざっと把握したところで実際のソースコードがどのような感じになっているか見てみましょう(といってもディレクトリだけだけど)\\\n`packages` というディレクトリにメインのソースコードが詰まっています．\n\nhttps://github.com/vuejs/core/tree/main/packages\n\n中でも注目したいのは，\n\n- compiler-core\n- compiler-dom\n- compiler-sfc\n- reactivity\n- runtime-core\n- runtime-dom\n- vue\n\nです．\nそれぞれの依存関係についてはコントリビュートガイドのこの図がとてもわかりやすいです．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n<br/>\nこの本では一通りこれらについての実装と解説を行います。\n"
  },
  {
    "path": "book/online-book/src/ja/00-introduction/040-setup-project.md",
    "content": "# 本書の進め方と環境構築\n\n## Web Playground について\n\n本書では，各チャプターの実装コードをブラウザ上で直接試せる **Web Playground** を用意しています．\n環境構築なしで，すぐにコードを編集・実行できるので，まずはこちらで chibivue の動作を体験してみてください！\n\n### Playground の起動方法\n\n```sh\n$ git clone https://github.com/chibivue-land/chibivue\n$ cd chibivue\n$ pnpm install\n$ pnpm play\n```\n\nブラウザで表示された URL (例: `http://localhost:5173/`) にアクセスすると Playground が起動します．\n\n### Playground の構成\n\n![Initial Web Playground screen](/figures/00-introduction/setup-project/web-playground-initial.png)\n\nPlayground は以下の 4 つのエリアで構成されています．\n\n| エリア | 説明 |\n|--------|------|\n| **Explorer (左)** | プロジェクトのファイルツリーを表示します．ファイルをクリックするとエディタで開きます |\n| **Editor (中央)** | Monaco Editor でコードを編集できます |\n| **Preview (右)** | WebContainer 上で動作する開発サーバーのプレビューを表示します |\n| **Terminal / Console (下)** | ターミナル出力と console.log の内容を確認できます |\n\n### 使い方\n\n1. **チャプターを選択する**\n   画面上部のドロップダウンから学習したいチャプターを選択します．\n   検索ボックスでチャプター名を絞り込むこともできます．\n\n2. **Run をクリックする**\n   「Run」ボタンをクリックすると，WebContainer が起動し，依存関係のインストールと開発サーバーの起動が行われます．\n   初回は少し時間がかかりますが，しばらく待つと Preview エリアに結果が表示されます．\n\n3. **コードを編集する**\n   エディタでコードを編集し，「Apply」ボタンをクリックすると変更が適用されます．\n   HMR (Hot Module Replacement) により，変更がリアルタイムで反映されます．\n\n4. **コンソールを確認する**\n   「Console」タブをクリックすると，console.log などの出力を確認できます．\n\n![Web Playground console output](/figures/00-introduction/setup-project/web-playground-console.png)\n\n::: tip\nWeb Playground は [WebContainer](https://webcontainers.io/) を使用しています．\n一部のブラウザや環境では動作しない場合があります．その場合は，以下のローカル環境構築を参考にしてください．\n:::\n\n## 本書の進め方\n\nこれから早速 Vue.js の実装を小さく行なっていきます．  \nそれに伴う心構えや注意点，その他知っておくべき情報を以下に列挙します．\n\n- プロジェクト名は chibivue とします．  \n  本書で実装する Vue.js の基本実装をまとめて chibivue と呼ぶことにします．\n- 基本方針は最初に話した通り，「小さい開発を繰り返す」です．\n- この本の付録として各フェーズのソースコードを https://github.com/chibivue-land/chibivue/tree/main/book/impls に載せてあります．  \n  この本では具体的な説明を全てのソースコードに対して行うわけではないので，その辺りは随時こちらを参照していただければと思います．\n- 完成系のコードはいくつかのパッケージに依存しています．  \n  これは自作系のコンテンツにありがちな問題なのですが，「どこからどこまで自分の手で実装すれば自作と言えるのだろう」という議論がしばしば挙げられます．  \n  例によってこの本も全てのソースコードを手で書くわけではありません．  \n  今回は Vue.js 本家のコードが使っているようなパッケージは積極的に使っていきます．例えば，[Babel](https://babeljs.io/) がその一つです．  \n  しかし安心してもらいたいのは，今回の本では前程知識を必要としないことを目指しているので必要になったパッケージについて必要最低限説明を加えます．\n\n## 環境構築\n\nさて，早速ですが環境構築からやっていきましょう！\\\n一応先に今回構築する環境の内容を列挙しておきます\n\n- ランタイム: [Node.js](https://nodejs.org/en) v24\n- 言語: [TypeScript](https://www.typescriptlang.org/)\n- パッケージマネージャ: [pnpm](https://pnpm.io/) v10\n- ビルドツール: [Vite](https://vite.dev/) v8\n\n## Node.js インストール\n\nおそらくここは大丈夫でしょう．各自で用意してください．\n説明については省略します．\n\n## pnpm のインストール\n\nもしかすると普段は npm や yarn を使っている方が多いかもしれません．  \n今回は pnpm を使っていくので，こちらの方も合わせてインストールしてください．  \n基本的なコマンドは npm とほとんど一緒です．  \nhttps://pnpm.io/installation\n\n\n## プロジェクトの作成\n\n::: details 手っ取り早くスタートしたい ...\nこれから，手動でプロジェクトを作成する手順を説明するのですが，実は構築用のツールも用意しています．  \n面倒くさい方は是非こちらを使ってください！\n\n1. chibivue をクローンする\n\n   ```sh\n   $ git clone https://github.com/chibivue-land/chibivue\n   ```\n\n2. script を実行.  \n   セットアップしたいディレクトリのパスを入力してください.\n\n   ```sh\n   $ cd chibivue\n   $ pnpm setup:book ../my-chibivue-project\n   ```\n\n:::\n\n任意のディレクトリでプロジェクトを作成します．\nここからは便宜上プロジェクトのルートパスを`~`と表現します．(例: `~/src/main.ts`など)\n\n今回は，chibivue の本体と動作を確認するためのプレイグラウンドを分けて実装してみます．\nといってもプレイグラウンド側で chibivue を呼び出して vite でバンドルするだけです．\nこのような構成にする想定です．\n\n```\n\n~\n|- examples\n| |- playground\n|\n|- packages\n|- tsconfig.json\n\n```\n\nexamples というディレクトリにプレイグラウンドを実装します．\npackages に chibivue 本体の TypeScript ファイル群を実装して，example 側からそれを import する形にします．\n\n以下はそれを構築する手順です．\n\n### プロジェクト本体の構築\n\n```sh\n## 実際はchibivue用のディレクトリを作って移動してください (以下、同様の注釈は省略します。)\npwd # ~/\npnpm init\npnpm add -D @types/node\nmkdir packages\ntouch packages/index.ts\ntouch tsconfig.json\n```\n\ntsconfig.json の内容\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\npackages/index.ts の内容\n\n```ts\nconsole.log(\"Hello, World\");\n```\n\n### プレイグラウンド側の構築\n\n```sh\npwd # ~/\nmkdir examples\ncd examples\npnpm dlx create-vite\n\n## --------- create vite cliの設定\n## Project name: playground\n## Select a framework: Vanilla\n## Select a variant: TypeScript\n```\n\nvite で作成したプロジェクトのうち，不要なものを削除します．\n\n```sh\npwd # ~/examples/playground\nrm -rf public\nrm -rf src # 不要なファイルがあるので一旦作り直します。\nmkdir src\ntouch src/main.ts\n```\n\nsrc/main.ts の中身\n※ 一旦 from の後ろのエラーが出ますがこれから設定するので問題ありません．\n\n```ts\nimport \"chibivue\"\n```\n\nindex.html を以下のように書き換えます．\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\nVite で作成したプロジェクトで chibivue で実装したものを import できるようにエイリアスの設定をします．\n\n```sh\npwd # ~/examples/playground\ntouch vite.config.js\n```\n\nvite.config.js の内容\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n})\n```\n\ntsconfig.json の中身を以下のように書き換えます．\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\n      \"ESNext\",\n      \"DOM\"\n    ],\n    \"moduleResolution\": \"Node\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\n        \"../../packages\"\n      ],\n    }\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n```\n\n最後に，chibivue プロジェクトの package.json に playground を起動するコマンドを記述して実際に起動してみましよう！\n\n~/package.json に以下を追記\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  }\n}\n```\n\n```sh\npwd # ~\npnpm dev\n```\n\nこのコマンドで立ち上がった開発者サーバーにアクセスし，メッセージが表示されていれば完了です！\n\n![Hello chibivue rendered in the browser](/figures/00-introduction/setup-project/hello-chibivue-result.png)\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/00_introduction/010_project_setup)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/010-create-app-api.md",
    "content": "# 初めてのレンダリングと createApp API\n\n## 何から始めよう?\n\nさて，ここからどんどん chibivue を実装していきます．\nどのように実装していくのがいいでしょうか ?\n\n<KawaikoNote variant=\"question\" title=\"いよいよ実装開始！\">\n\nVue.js の内部実装に挑戦するワクワクの瞬間です！\n「どこから始めるか」が意外と大事なポイントになります．\n\n</KawaikoNote>\n\nこれは著者がいつも心がけていることですが，何か既存のライブラリやソフトウェアを自作するときにはまずそのソフトウェアはどうやって使うのかということから考えます．  \nこの，「ソフトウェアを実際に使うときのインタフェース」のことをここからは便宜上「`開発者インタフェース`」と呼ぶことにします．  \nここでいう「開発者」とは，chibivue の開発者のことではなく，chibivue を使って Web アプリケーションを開発する人のことです．  \nつまりは chibivue を開発するにあたって今一度本家 Vue.js の開発者インタフェースを参考にしてみます．  \n具体的には Vue.js で Web アプリケーションを開発する際にまず何を書くかというところを見てみます．\n\n## 開発者インタフェースのレベル?\n\nここで気をつけたいのは，Vue.js には複数の開発者インタフェースがあり，それぞれレベルが違うということです．  \nここでいうレベルというのは「どれくらい生の JavaScript に近いか」ということです．  \n例えば，Vue で HTML を表示するための開発者インタフェースの例として以下のようなものが挙げられます．\n\n1. Single File Component で template を書く\n\n```vue\n<!-- App.vue -->\n<template>\n  <div>Hello world.</div>\n</template>\n```\n\n```ts\nimport { createApp } from 'vue'\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\n2. template オプションを使用する\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  template: '<div>Hello world.</div>',\n})\n\napp.mount('#app')\n```\n\n3. render オプションと h 関数を利用する\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\n他にもありますが，このような 3 つの開発者インタフェースについて考えてみます．  \nどれが一番生の JavaScript に近いでしょうか?  \n答えは，3 の `render オプションと h 関数を利用する` です．  \n1 は SFC のコンパイラやそれらをバンドルするバンドラやローダの実装が必要ですし，2 は template に渡された HTML をコンパイル (そのままでは動かないので JS のコードに変換) する必要があります．\n\nここでは便宜上，生の JS に近ければ近いほど「`低級な開発者インタフェース`」と呼ぶことにします．\nそして，ここで重要なのが，「実装を始めるときは低級なところから実装していく」ということです．\nそれはなぜかというと，多くの場合，高級な記述は低級な記述に変換されて動いているからです．\nつまり，1 も 2 も最終的には内部的に 3 の形に変換しているのです．\nその変換の実装のことを「コンパイラ (翻訳機)」と呼んでいます．\n\n<KawaikoNote variant=\"funny\" title=\"低級から攻める！\">\n\n「低級」というとなんだか弱そうですが，実は逆！\n基盤となる部分なので，ここがしっかりしていないと上位の機能が作れません．\n\n</KawaikoNote>\n\nということで，まずは 3 のような開発者インタフェースを目指して実装していきましょう!\n\n## createApp API とレンダリング\n\n3 の形を目指すとはいったもののまだ h 関数についてはよく分かっていないですし，なんといってもこの本はインクリメンタルな開発を目指しているので，  \nいきなり 3 の形を目指すのはやめて，以下のような形で render 関数ではメッセージを return してそれを表示するだけの実装をしてみましょう．\n\nイメージ ↓\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n## 早速実装\n\n`~/packages/index.ts` に createApp 関数を作ってみましょう．\\\n※ `\"Hello, World\"` の出力は不要なので消してしまいます．\n\n```ts\nexport type Options = {\n  render: () => string\n}\n\nexport type App = {\n  mount: (selector: string) => void\n}\n\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render()\n      }\n    },\n  }\n}\n```\n\nとても簡単ですね．playground の方で試してみましょう．\n\n`~/examples/playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n画面にメッセージを表示することができました! やったね!\n\n![createApp example rendered in the browser](/figures/10-minimum-example/create-app-api/hello-create-app-result.png)\n\n<KawaikoNote variant=\"surprise\" title=\"第一歩完了！\">\n\nたった数十行のコードで Vue.js 風のアプリが動きました！\nこの小さな一歩が，フレームワーク理解への大きな一歩です．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/010_create_app)\n\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/015-package-architecture.md",
    "content": "# パッケージの設計\n\n## リファクタリング\n\n「え？まだこれだけしか実装していないのにリファクタするの？」と思うかもしれませんが，この本の目的の一つに「Vue.js のソースコードを読めるようになる」というものがありました．  \nそれに伴って，ファイルやディレクトリ構成も Vue.js の形を常に意識したいわけです．  \nなので，少しばかりリファクタさせてください．．．\n\n### Vue.js の設計\n\n#### runtime-core と runtime-dom\n\nここで少し Vue.js 本家の構成についての説明です．\n今回のリファクタでは `runtime-core` というディレクトリと `runtime-dom` というディレクトリを作ります．\n\n<KawaikoNote variant=\"question\" title=\"なぜ分けるの？\">\n\n「動くコードができたのに，なぜわざわざ分割するの？」と思うかもしれません．\\\n実は Vue.js はブラウザだけでなく，サーバーサイドレンダリング（SSR）やネイティブアプリ（Vue Native）など，様々な環境で動作できるように設計されています．\\\nそのために，DOM に依存しない「純粋なロジック」と「DOM 操作」を分離しているのです．\n\n</KawaikoNote>\n\nそれぞれなんなのかというと，runtime-core というのは，Vue.js のランタイム機能のうち本当にコアになる機能が詰まっています．\nと言われても何がコアで何がコアじゃないのか今の段階だとわかりづらいと思います．\n\nなので，runtime-dom との関係を見てみるとわかりやすいかなと思います．  \nruntime-dom というのは名前の通り，DOM に依存した実装を置くディレクトリです．ざっくり「ブラウザに依存した処理」という理解をしてもらえれば問題ないです．  \n例を挙げると querySelector や createElement などの DOM 操作が含まれます．\n\nruntime-core ではそういった処理は書かず，あくまで純粋な TypeScript の世界の中で Vue.js のランタイムに関するコアロジックを記述するような設計になっています．  \n例を挙げると，仮想 DOM に関する実装であったり，コンポーネントに関する実装だったりです．  \nこの辺りに関しては chibivue の開発が進むにつれて明確になってくると思うのでわからなかったらとりあえず本の通りにリファクタしてもらえれば問題ありません．\n\n#### 各ファイルの役割と依存関係\n\nこれから runtime-core と runtime-dom にいくつかファイルを作ります．必要なファイルは以下のとおりです．\n\n```sh\npwd # ~\nmkdir packages/runtime-core\nmkdir packages/runtime-dom\n\n## core\ntouch packages/runtime-core/index.ts\ntouch packages/runtime-core/apiCreateApp.ts\ntouch packages/runtime-core/component.ts\ntouch packages/runtime-core/componentOptions.ts\ntouch packages/runtime-core/renderer.ts\n\n## dom\ntouch packages/runtime-dom/index.ts\ntouch packages/runtime-dom/nodeOps.ts\n```\n\nこれらの役割についてですが，最初から文章で説明してもわかりづらいかと思いますので以下の図を見てください．\n\n![runtime-core and runtime-dom responsibilities](/figures/10-minimum-example/package-architecture/runtime-core-dom-overview.svg)\n\n#### renderer の設計\n\n先ほども話したとおり，Vue.js では DOM に依存する部分と純粋な Vue.js のコア機能部分を分離しています．\nまず，注目して欲しいのは `runtime-core` の方の renderer factory と `runtime-dom` の nodeOps です．\n先ほど実装した例だと，createApp が返す app の mount メソッドで直接レンダリングをしていました．\n\n```ts\n// これは先ほどのコード\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render() // レンダリング\n      }\n    },\n  }\n}\n```\n\nここまでではコードも少なく，全く複雑ではないので一見問題ないように見えます．  \nですが，今後は仮想 DOM のパッチレンダリングのロジック等を書くことになるのでかなり複雑になります．\nVue.js ではこのレンダリングを担う部分を `renderer` として切り出しています．\nそれが `runtime-core/renderer.ts` です．\nレンダリングというと SPA においてはブラウザの DOM を司る API (document) に依存することが安易に想像できると思います．\\\n(element を作ったり text をセットしたり)\\\nそこで，この DOM に依存する部分と Vue.js が持つコアなレンダーロジックを切り離すために，いくつかの工夫がしてあります．\\\n以下のとおりです．\n\n- `runtime-dom/nodeOps` に DOM 操作をするためのオブジェクトを実装する\n- `runtime-core/renderer` ではあくまで，render のロジックのみを持つオブジェクトを生成するためのファクトリ関数を実装する．  \n  その際，Node (DOM に限らず) を扱うオブジェクトは factory の関数の引数として受け取るようにしてある．\n- `runtime-dom/index.ts` で nodeOps と renderer のファクトリをもとに renderer を完成させる\n\nここまでの話が図の赤く囲まれた部分です．\n![Renderer dependency injection](/figures/10-minimum-example/package-architecture/renderer-dependency-injection.svg)\n\nソースコードベースで説明してみます．今の時点ではまだ仮想 DOM のレンダリング機能は実装していないので，先ほどと同じ機能で作ります．\n\nまず，`runtime-core/renderer`に Node(DOM に限らず)のオペレーション用オブジェクトの interface を実装します．\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  setElementText(node: HostNode, text: string): void\n}\n\nexport interface RendererNode {\n  [key: string]: any\n}\n\nexport interface RendererElement extends RendererNode {}\n```\n\nここではまだ setElementText という関数しかありませんが，ゆくゆくは createElement だったり，removeChild などが実装されるイメージをしてもらえれば大丈夫です．\n\nRendererNode と RendererElement については一旦気にしないでください．(ここの実装はあくまで DOM に依存してはいけないので，Node となるものを定義してジェネリックにしているだけです．)  \nこの，RendererOptions を受け取る形で renderer のファクトリをこのファイルに実装します．\n\n```ts\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  message: string,\n  container: HostElement,\n) => void\n\nexport function createRenderer(options: RendererOptions) {\n  const { setElementText: hostSetElementText } = options\n\n  const render: RootRenderFunction = (message, container) => {\n    hostSetElementText(container, message) // 今回はメッセージを挿入するだけなのでこういう実装になっている\n  }\n\n  return { render }\n}\n```\n\n続いて，`runtime-dom/nodeOps` 側の実装です．\n\n```ts\nimport { RendererOptions } from '../runtime-core'\n\nexport const nodeOps: RendererOptions<Node> = {\n  setElementText(node, text) {\n    node.textContent = text\n  },\n}\n```\n\n特に難しいことはないと思います．\n\nそれでは，`runtime-dom/index.ts` で renderer を完成させましょう．\n\n```ts\nimport { createRenderer } from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\n```\n\nこれで renderer 部分のリファクタは終わりです．\n\n#### DI と DIP\n\nrenderer の設計を見てみました．改めて整理をしておくと，\n\n- runtime-core/renderer に renderer を生成するファクトリ関数を実装\n- runtime-dom/nodeOps に DOM に依存するオペレーション (操作) をするためのオブジェクトを実装\n- runtime-dom/index にてファクトリ関数と nodeOps を組み合わせて renderer を生成\n\nといった感じでした．\n一般的にはこのような設計を「DIP」を利用した「DI」と言います．\n\n<KawaikoNote variant=\"warning\" title=\"少し難しい話です\">\n\nDI と DIP は設計パターンの中でも難しい概念です．\\\n最初は「なんとなくこういうものか」程度の理解で大丈夫！\\\nコードを書いていくうちに「あ，こういうことか！」と分かってきます．\n\n</KawaikoNote>\n\nまず，DIP についてですが，DIP (Dependency inversion principle) インタフェースを実装することにより，依存性の逆転を行います．\n注目するべきところは，renderer.ts に実装した `RendererOptions` という interface です．\nファクトリ関数も，nodeOps もこの `RendererOptions` を守るように実装します．(RendererOptions というインタフェースに依存させる)\n\n<KawaikoNote variant=\"funny\" title=\"例えで考えてみよう\">\n\nDIP を料理で例えると…\\\n「レシピ（interface）」があれば，材料が国産でも輸入品でも同じ料理が作れますよね．\\\nrenderer（料理人）は「RendererOptions（レシピ）」に従うだけで，実際の材料（DOM 操作 or 他の操作）を気にしなくて良いのです．\n\n</KawaikoNote>\n\nこれを利用して DI を行います．DI (Dependency Injection) はあるオブジェクトが依存しているあるオブジェクトを外から注入することによって依存度を下げるテクニックです．\n今回のケースでいうと，renderer は RendererOptions (を実装したオブジェクト(今回でいえば nodeOps)) に依存しています．\nこの依存性を renderer から直接呼び出して実装するのはやめて，ファクトリの引数として受け取る (外から注入する) ようにしています．\nこれらのテクニックによって renderer が DOM に依存しないような工夫をとっています．\n\n<KawaikoNote variant=\"base\" title=\"まとめると\">\n\n**DIP**: 具体的な実装ではなく，interface（約束事）に依存させる\\\n**DI**: 依存するものを外から渡してもらう（注入する）\n\nこの 2 つを組み合わせることで，柔軟で テストしやすいコードが書けます！\n\n</KawaikoNote>\n\nDI と DIP は慣れていないと難しい概念かもしれませんが，よく出てくる重要なテクニックなので各自で調べてもらったりして理解していただけると幸いです．\n\n### createApp を完成させる\n\n実装に話を戻して，renderer が生成できたのであとは以下の図の赤い領域について考えれば良いです．\n\n![createAppAPI factory flow](/figures/10-minimum-example/package-architecture/create-app-api-factory.svg)\n\nと，いってもやることは単純で，createApp のファクトリ関数に先ほど作った renderer を渡せるように実装すれば良いだけです．\n\n```ts\n// ~/packages/runtime-core apiCreateApp.ts\n\nimport { Component } from './component'\nimport { RootRenderFunction } from './renderer'\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void\n}\n\nexport type CreateAppFunction<HostElement> = (\n  rootComponent: Component,\n) => App<HostElement>\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const message = rootComponent.render!()\n        render(message, rootContainer)\n      },\n    }\n\n    return app\n  }\n}\n```\n\n```ts\n// ~/packages/runtime-dom/index.ts\n\nimport {\n  CreateAppFunction,\n  createAppAPI,\n  createRenderer,\n} from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\nconst _createApp = createAppAPI(render)\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args)\n  const { mount } = app\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector)\n    if (!container) return\n    mount(container)\n  }\n\n  return app\n}) as CreateAppFunction<Element>\n```\n\n多少 `~/packages/runtime-core/component.ts` 等に型を移動してますが，その辺はあまり重要ではないのでソースコードを参照してもらえればと思います．\\\n(本家 Vue.js に合わせているだけです．)\n\nだいぶ本家 Vue.js のソースコードに近づいたところで動作確認をしてみましょう．変わらずメッセージが表示されていれば OK です．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/015_package_architecture)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/020-simple-h-function.md",
    "content": "# HTML要素をレンダリングできるようにしよう\n\n## h function とは\n\n<KawaikoNote variant=\"question\" title=\"h って何の略？\">\n\n`h` は `hyperscript` の略です．HTML (Hyper Text Markup Language) を\nJavaScript で表現するための関数なので，この名前がついています！\n\n</KawaikoNote>\n\nここまでで，以下のようなソースコードが動作するようになりました．\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\nこれはシンプルな `Hello World.` と画面に描画するための関数でした．  \nメッセージだけでは何とも寂しいので，HTML 要素も描画できるような開発者インタフェースを考えてみましょう．  \nそこで登場するのが `h function` です．この `h` というのは `hyperscript` の略で，HTML (Hyper Text Markup Language)を JS で記述する関数として提供されます．\n\n> h() is short for hyperscript - which means \"JavaScript that produces HTML (hypertext markup language)\". This name is inherited from conventions shared by many Virtual DOM implementations. A more descriptive name could be createVnode(), but a shorter name helps when you have to call this function many times in a render function.\n\n引用: https://vuejs.org/guide/extras/render-function.html#creating-vnodes\n\nVue.js の h function についてみてみましょう．\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['HelloWorld']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nh function の基本的な使い方として，第 1 引数にタグ名，第 2 引数に属性，第 3 引数に子要素を配列で記述します．  \nここで，「基本的な使い方」とわざわざ言ったのは，実は h function は引数について記法が複数あり，第 2 引数を省略したり，子要素は配列にしなかったりという使い方もできます．  \nですが，ここでは最も基本的な記法に統一して実装してみようかと思います．\n\n## どうやって実装しよう\n\n開発者インタフェースについてはよくわかったので，どのような実装にするか方針を決めましょう．  \n注目するべき点は，render 関数の戻り値として扱っているところです．  \nこれはつまり，h 関数というものが何かしらのオブジェクトを返して内部でその結果を利用しているということです．\n複雑な子要素を含むとわかりづらいので，以下のシンプルな h 関数を実装した結果について考えてみましょう．\n\n```ts\nconst result = h('div', { class: 'container' }, ['hello'])\n```\n\nresult にはどのような結果を格納するのが良いでしょうか？\\\n(結果をどのような形にして，どうレンダリングしましょうか？)\n\nresult には以下のようなオブジェクトが格納されることにしてみましょう．\n\n```ts\nconst result = {\n  type: 'div',\n  props: { class: 'container' },\n  children: ['hello'],\n}\n```\n\nつまり，render 関数から上記のようなオブジェクトをもらい，それを元に DOM 操作をしてレンダリングをすればいいのです．\\\nイメージ的には以下です．(createApp の mount の中です．)\n\n```ts\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const node = rootComponent.render!()\n    render(node, rootContainer)\n  },\n}\n```\n\n変わったところというと，message という文字列ではなく node というオブジェクトに変えただけです．  \nあとは render 関数でオブジェクトを元に DOM 操作をすれば OK です．\n\n実は，このオブジェクトには名前がついていて，「仮想 DOM」と言います．\n仮想 DOM については仮想 DOM のチャプターで詳しく解説するので，とりあえず名前だけ覚えてもらえれば大丈夫です．\n\n<KawaikoNote variant=\"funny\" title=\"仮想 DOM の正体\">\n\n「仮想 DOM」と聞くと難しそうですが，中身はただの JavaScript オブジェクト！\n`{ type, props, children }` という構造で DOM を表現しているだけです．\n\n</KawaikoNote>\n\n## h function を実装する\n\nまずは必要なファイルを作成します．\n\n```sh\npwd # ~\ntouch packages/runtime-core/vnode.ts\ntouch packages/runtime-core/h.ts\n```\n\nvnode.ts に型を定義します．今回 vnode.ts でやるのはこれだけです．\n\n```ts\nexport interface VNode {\n  type: string\n  props: VNodeProps\n  children: (VNode | string)[]\n}\n\nexport interface VNodeProps {\n  [key: string]: any\n}\n```\n\n続いて h.ts で関数本体を実装します．\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return { type, props, children }\n}\n```\n\nとりあえずここまでで playground にて h 関数を使ってみましょう．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\n画面の表示は壊れてしまっていますが，apiCreateApp でログを仕込んでみると期待通りになっていることが確認できます．\n\n```ts\nmount(rootContainer: HostElement) {\n  const vnode = rootComponent.render!();\n  console.log(vnode); // ログを見てみる\n  render(vnode, rootContainer);\n},\n```\n\nそれでは，render 関数を実装してみましょう．\nRendererOptions に `createElement` と `createText` と `insert` を実装します．\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  createElement(type: string): HostNode // 追加\n\n  createText(text: string): HostNode // 追加\n\n  setElementText(node: HostNode, text: string): void\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void // 追加\n}\n```\n\nrender 関数に `renderVNode` という関数を実装してみます．\\\n(とりあえず一旦 props は無視して実装しています．)\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  const {\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options\n\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === 'string') return hostCreateText(vnode)\n    const el = hostCreateElement(vnode.type)\n\n    for (const child of vnode.children) {\n      const childEl = renderVNode(child)\n      hostInsert(childEl, el)\n    }\n\n    return el\n  }\n\n  const render: RootRenderFunction = (vnode, container) => {\n    const el = renderVNode(vnode)\n    hostInsert(el, container)\n  }\n\n  return { render }\n}\n```\n\nruntime-dom の nodeOps の方でも実際の DOM のオペレーションを定義してあげます．\n\n```ts\nexport const nodeOps: RendererOptions<Node> = {\n  // 追加\n  createElement: tagName => {\n    return document.createElement(tagName)\n  },\n\n  // 追加\n  createText: (text: string) => {\n    return document.createTextNode(text)\n  },\n\n  setElementText(node, text) {\n    node.textContent = text\n  },\n\n  // 追加\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\nさて，ここまでで画面に要素を描画できるようになっているはずです．\\\nplayground で色々書いてみて試してみましょう!\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nやった！ h 関数でいろんなタグを描画できるようになった！\n\n![VNode log from a simple h function](/figures/10-minimum-example/simple-h-function/basic-vnode-log.png)\n\n<KawaikoNote variant=\"surprise\" title=\"h 関数完成！\">\n\nこれで HTML を JavaScript で表現できるようになりました！\n入れ子構造でどんな複雑な画面も作れます．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/025-event-handler-and-attrs.md",
    "content": "# イベントハンドラや属性に対応してみる\n\n<KawaikoNote variant=\"question\" title=\"props って何？\">\n\nprops は「プロパティ」の略で，要素に渡す情報のことです．\nイベントハンドラ（onClick など）や属性（style, class など）も props として扱います！\n\n</KawaikoNote>\n\n## 表示するだけでは寂しいので\n\nせっかくなので簡単な props の実装をしてクリックイベントやスタイルを使えるようにしてみます．\n\nこの部分について，直接 renderVNode に実装してしまってもいいのですが，本家に倣った設計も考慮しつつ進めてみようかと思います．\n\n本家 Vue.js の runtime-dom ディテクトリに注目してください．\n\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src\n\n特に注目して欲しいのは `modules` というディレクトリと `patchProp.ts` というファイルです．\n\nmodules の中には class や style, その他 props の操作をするためのファイルが実装されています．\\\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src/modules\n\nそれらを patchProp という関数にまとめているのが patchProp.ts で，これを nodeOps に混ぜ込んでいます．\n\n言葉で説明するのも何なので，実際にこの設計に基づいてやってみようと思います．\n\n## patchProps のガワを作成\n\nまずガワから作ります．\n\n```sh\npwd # ~\ntouch packages/runtime-dom/patchProp.ts\n```\n\n`runtime-dom/patchProp.ts` の内容\n\n```ts\ntype DOMRendererOptions = RendererOptions<Node, Element>\n\nconst onRE = /^on[^a-z]/\nexport const isOn = (key: string) => onRE.test(key)\n\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    // patchEvent(el, key, value); // これから実装します\n  } else {\n    // patchAttr(el, key, value); // これから実装します\n  }\n}\n```\n\n`RendererOptions` に patchProp の型がないので定義します．\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement\n> {\n  // 追加\n  patchProp(el: HostElement, key: string, value: any): void;\n  .\n  .\n  .\n```\n\nそれに伴って，nodeOps では patchProps 以外の部分を使用するように書き換えます．\n\n```ts\n// patchPropをomitする\nexport const nodeOps: Omit<RendererOptions, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n  .\n  .\n  .\n```\n\nそして，`runtime-dom/index` の renderer を生成する際に patchProp も一緒に渡すように変更します．\n\n```ts\nconst { render } = createRenderer({ ...nodeOps, patchProp })\n```\n\n## イベントハンドラ\n\npatchEvent を実装します．\n\n```sh\npwd # ~\nmkdir packages/runtime-dom/modules\ntouch packages/runtime-dom/modules/events.ts\n```\n\nevents.ts を実装します．\n\n```ts\ninterface Invoker extends EventListener {\n  value: EventValue\n}\n\ntype EventValue = Function\n\nexport function addEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.addEventListener(event, handler)\n}\n\nexport function removeEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.removeEventListener(event, handler)\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {})\n  const existingInvoker = invokers[rawName]\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value\n  } else {\n    const name = parseName(rawName)\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value))\n      addEventListener(el, name, invoker)\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker)\n      invokers[rawName] = undefined\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase()\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e)\n  }\n  invoker.value = initialValue\n  return invoker\n}\n```\n\n少し大きいですが，分割すればとても単純なことです．\n\naddEventListener は名前の通り，ただイベントのリスナーを登録するための関数です．  \n本当は然るべきタイミングで remove する必要があるのですが，ここでは一旦気にしないことにします．\n\npatchEvent では invoker という関数でラップしてリスナーを登録しています．  \nparseName に関しては，単純に props のキー名は `onClick` や `onInput` のようになっているので，それらを on を除いた小文字に変換しているだけです．(eg. click, input)  \n一点注意点としては，同じ要素に対して重複して addEventListener しないように，要素に `_vei` (vue event invokers) という名前で invoker を生やしてあげます．  \nこれによって patch 時に existingInvoker.value を更新することで重複して addEventListener せずにハンドラを更新することができます．\\\ninvoker と言うのは単に「実行する者」と言う者です．特に深い意味はありません，実際に実行されるハンドラを格納するためのオブジェクトです．\n\nあとは patchProps に組み込んで renderVNode で使ってみましょう．\n\npatchProps\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value)\n  } else {\n    // patchAttr(el, key, value); // これから実装します\n  }\n}\n```\n\nruntime-core/renderer.ts の renderVNode\n\n```ts\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options;\n  .\n  .\n  .\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === \"string\") return hostCreateText(vnode);\n    const el = hostCreateElement(vnode.type);\n\n    // ここ\n    Object.entries(vnode.props).forEach(([key, value]) => {\n      hostPatchProp(el, key, value);\n    });\n    .\n    .\n    .\n```\n\nさて，playground で動かしてみましょう．簡単にアラートを表示してみようと思います．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\nh 関数でイベントハンドラを登録できるようになりました！\n\n![Event handler example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/event-handler-result.png)\n\n<KawaikoNote variant=\"funny\" title=\"invoker の工夫\">\n\nイベントハンドラを直接登録すると，更新のたびに remove/add が必要です．\ninvoker でラップすることで，value を差し替えるだけで済むようになります！\n\n</KawaikoNote>\n\n## 他の Props にも対応してみる．\n\nあとは同じようなことを setAttribute でやるだけです．  \nこれは `modules/attrs.ts` に実装します．  \nここはぜひみなさんでやってみてください．答えは最後にこのチャプターのソースコードを添付するのでそこで確認してみてください．  \nこれくらいのコードが動くようになればゴールです．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', { id: 'my-app' }, [\n      h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n![Attribute patching example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/attrs-result.png)\n\nこれでかなりの HTML に対応することができました!\n\n<KawaikoNote variant=\"surprise\" title=\"props 対応完了！\">\n\nイベントと属性に対応したことで，インタラクティブな UI が作れるようになりました！\nここからアプリらしくなってきますね．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system.md",
    "content": "# リアクティビティシステムの前程知識\n\n## 今回目指す開発者インタフェース\n\nここからは Vue.js の醍醐味であるリアクティビティシステムというものについてやっていきます．\n\n<KawaikoNote variant=\"surprise\" title=\"いよいよ本番！\">\n\nここからが Vue.js の真髄です！\\\nリアクティビティシステムを理解すれば，Vue.js の「魔法」の正体がわかります．\\\n少し難しいですが，一緒に頑張りましょう！\n\n</KawaikoNote>\n\nこれ以前の実装は，見た目が Vue.js に似ていれど，それは見た目だけで機能的には全く Vue.js ではありません．\nたんに最初の開発者インタフェースを実装し，いろんな HTML を表示できるようにしてみました．\n\nしかし，このままでは一度画面を描画するとその後はそのままで，Web アプリケーションとしてはただの静的なサイトになってしまっています．  \nこれから，もっとリッチな UI を構築するために状態を持たせたり，その状態が変わったら描画を更新したりといったことをやっていきます．\n\nまずは例の如くどういった開発者インタフェースになるか考えてみましょう．  \n以下のようなのはどうでしょうか?\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n\n    const increment = () => {\n      state.count++\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n普段 SFC を利用した開発を行っている方は少々見慣れないかもしれません．  \nこれは，setup というオプションでステートをもち，render 関数を return する開発者インタフェースです．  \n実際，Vue.js にはこういった記法があります．\n\nhttps://vuejs.org/api/composition-api-setup.html#usage-with-render-functions\n\nreactive 関数でステートを定義し，それを書き換える increment という関数を実装してボタンの click イベントにバインドしています．\nやりたいことをまとめておくと，\n\n- setup 関数を実行することで戻り値から vnode 取得用の関数を得る\n- reactive 関数に渡したオブジェクトをリアクティブにする\n- ボタンをクリックすると，ステートが更新される\n- ステートの更新を追跡して render 関数を再実行し，画面を再描画する\n\n## リアクティビティシステムとはどのようなもの？\n\nさてここで，そもそもリアクティブとは何だったかのおさらいです．\\\n公式ドキュメントを参照してみます．\n\n> リアクティブなオブジェクトは JavaScript プロキシで、通常のオブジェクトと同じように振る舞います。違いは、Vue がリアクティブなオブジェクトのプロパティアクセスと変更を追跡できることです。\n\n[引用元](https://ja.vuejs.org/guide/essentials/reactivity-fundamentals.html)\n\n> Vue の最も特徴的な機能の 1 つは、控えめな Reactivity System です。コンポーネントの状態はリアクティブな JavaScript オブジェクトで構成されています。状態を変更すると、ビュー (View) が更新されます。\n\n[引用元](https://ja.vuejs.org/guide/extras/reactivity-in-depth.html)\n\n要約してみると，「リアクティブなオブジェクトは変更があった時に画面が更新される」です．  \nこれの実現方法について考えるのは少し置いておいて，とりあえず先ほどあげた開発者インタフェースを実装してみます．\n\n## setup 関数の実装\n\nやることはとっても簡単です．\nsetup オプションを受け取り実行し，あとはそれをこれまでの render オプションと同じように使えば OK です．\n\n~/packages/runtime-core/componentOptions.ts を編集します．\n\n```ts\nexport type ComponentOptions = {\n  render?: Function\n  setup?: () => Function // 追加\n}\n```\n\nあとはそれを使うように各コードを修正します．\n\n```ts\n// createAppAPI\n\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    updateComponent()\n  },\n}\n```\n\n```ts\n// playground\n\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // ゆくゆくはここでステートを定義\n    // const state = reactive({ count: 0 })\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n        h(\n          'button',\n          {\n            onClick() {\n              alert('Hello world!')\n            },\n          },\n          ['click me!'],\n        ),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nまあ，これだけです．\n実際にはステートが変更された時にこの `updateComponent` を実行したいわけです．\n\n## Proxy オブジェクト\n\n今回のメインテーマです．どうにかしてステートが変更された時に updateComponent を実行したいです．\n\nProxy と呼ばれるオブジェクトが肝になっています．\n\n<KawaikoNote variant=\"question\" title=\"Proxy って何？\">\n\nProxy は JavaScript の標準機能で，Vue.js 独自のものではありません．\\\n「オブジェクトへのアクセスを監視・カスタマイズできる仕組み」と覚えておきましょう！\\\nこれを使えば「値が読まれた」「値が変更された」を検知できるのです．\n\n</KawaikoNote>\n\nまず，リアクティビティシステムの実装方法についてではなく，それぞれについての説明をしてみます．\n\nhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy\n\nProxy はとても面白いオブジェクトです．\n\n以下のように，引数にオブジェクトを渡し，new することで使います．\n\n```ts\nconst o = new Proxy({ value: 1 }, {})\nconsole.log(o.value) // 1\n```\n\nこの例だと，`o` は通常のオブジェクトとほぼ同じ動作をします．\n\nここで，面白いのが，Proxy は第 2 引数を取ることができ，ハンドラを登録することができます．  \nこのハンドラは何のハンドラかというと，オブジェクトの操作に対するハンドラです．以下の例をみてください．\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n\n  {\n    get(target, key, receiver) {\n      console.log(`target:${target}, key: ${key}`)\n      return target[key]\n    },\n  },\n)\n```\n\nこの例では生成するオブジェクトに対する設定を書き込んでいます．  \n具体的には，このオブジェクトのプロパティにアクセス (get) した際に元のオブジェクト (target) とアクセスされた key 名がコンソールに出力されるようになっています．\n実際にブラウザ等で動作を確認してみましょう．\n\n![Proxy get trap console output](/figures/10-minimum-example/reactivity/proxy-get-trap.png)\n\nこの Proxy で生成したオブジェクトのプロパティから値を読み取った時に設定された処理が実行されているのがわかるかと思います．\n\n同様に，set に対しても設定することができます．\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n  {\n    set(target, key, value, receiver) {\n      console.log('hello from setter')\n      target[key] = value\n      return true\n    },\n  },\n)\n```\n\n![Proxy set trap console output](/figures/10-minimum-example/reactivity/proxy-set-trap.png)\n\n<KawaikoNote variant=\"funny\" title=\"これがリアクティビティの秘密！\">\n\nget で「読み取り」，set で「書き込み」を検知できる…\\\nつまり，set のタイミングで「画面を更新する処理」を呼べば，**値が変わったら自動で画面が更新される** という魔法が実現できるのです！\n\n</KawaikoNote>\n\nProxy の理解はこの程度で OK です．\n\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/035-try-implementing-a-minimum-reactivity-system.md",
    "content": "# 小さいリアクティビティシステムを実装してみる\n\n\n## Proxy を使ったリアクティビティの仕組み\n\n::: info 現在の vuejs/core との設計の違いについて\n現在 (2024/12)の Vue.js のリアクティビティシステムでは，doubly linked list ベースの Observer Pattern が採用されています．\\\nこの実装は [Refactor reactivity system to use version counting and doubly-linked list tracking](https://github.com/vuejs/core/pull/10397) で行われ，パフォーマンスの向上に寄与しました．\n\nしかし，初めてリアクティビティシステムを実装する人にとっては少し難しいものになっており，今回のこのチャプターでは従来 (改善以前) のものをより簡略化したものの実装を行います．\\\nより現在の実装に近いものは [リアクティビティの最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization) にて解説します．\n\nまた，もう一つ大きな改善として，[feat(reactivity): more efficient reactivity system](https://github.com/vuejs/core/pull/5912) がありますが，こちらも別のチャプターで解説します．\n:::\n\n改めて目的を明確にしておくと，今回の目的は「ステートが変更された時に `updateComponent` を実行したい」です．\nProxy を用いた実装の流れについて説明してみます．\n\nまず，Vue.js のリアクティビティシステムには `target`, `Proxy`, `ReactiveEffect`, `Dep`, `track`, `trigger`, `targetMap`, `activeEffect` (現在は `activeSub`) というものが登場します．\n\n<KawaikoNote variant=\"warning\" title=\"登場人物が多い！\">\n\nいきなりたくさんの用語が出てきましたが，焦らないでください！\\\n一つずつ役割を見ていけば，パズルのピースがはまるように理解できます．\\\nまずは「全体像をぼんやり掴む」ことを目指しましょう．\n\n</KawaikoNote>\n\nまず，targetMap の構造についてです．\ntargetMap はある target の key と dep のマッピングです．\ntarget というのはリアクティブにしたいオブジェクト，dep というのは実行したい作用(関数)だと思ってもらえれば大丈夫です．\nコードで表すとこういう感じになります．\n\n```ts\ntype Target = any // 任意のtarget\ntype TargetKey = any // targetが持つ任意のkey\n\nconst targetMap = new WeakMap<Target, KeyToDepMap>() // このモジュール内のグローバル変数として定義\n\ntype KeyToDepMap = Map<TargetKey, Dep> // targetのkeyと作用のマップ\n\ntype Dep = Set<ReactiveEffect> // depはReactiveEffectというものを複数持っている\n\nclass ReactiveEffect {\n  constructor(\n    // ここに実際に作用させたい関数を持たせます。 (今回でいうと、updateComponent)\n    public fn: () => T,\n  ) {}\n}\n```\n\n「ある target (オブジェクト)」 の「ある key」 に対して「ある作用」 を登録するということになります．\n\nパッと見のコードだけだと分かりづらいと思うので具体例と図による補足です．\\\n以下のようなコンポーネントがあったと考えてみます．\n\n```ts\nexport default defineComponent({\n  setup() {\n    const state1 = reactive({ name: \"John\", age: 20 })\n    const state2 = reactive({ count: 0 })\n\n    function onCountUpdated() {\n      console.log(\"count updated\")\n    }\n\n    watch(() => state2.count, onCountUpdated)\n\n    return () => h(\"p\", {}, `name: ${state1.name}`)\n  }\n})\n```\n\nこのチャプターではまだ watch は実装していないのですが，イメージのために書いてあります．\\\nこのコンポーネントでは最終的にこのような targetMap が形成されます．\n\n![targetMap structure](/figures/10-minimum-example/reactivity/target-map-structure.svg)\n\ntargetMap の key は「ある target」 です．この例では state1 と state2 がそれにあたります．\\\nそして，これらの target が持つ key が targetMap の key になります．\\\nそこに紐づく作用がその value になります．\n\n`() => h(\"p\", {}, name: ${state1.name})` の部分で `state1->name->updateComponentFn` というマッピングが登録され，`watch(() => state2.count, onCountUpdated)` の部分で `state2->count->onCountUpdated` というマッピングが登録されるという感じです．\n\n基本的な構造はこれが担っていて，あとはこの TargetMap をどう作っていくか(どう登録していくか)と実際に作用を実行するにはどうするかということを考えます．\n\n<KawaikoNote variant=\"funny\" title=\"シンプルに考えると\">\n\n**targetMap** は「誰が誰に影響を与えるか」を記録するメモ帳です．\\\n`state1.name` が変わったら → `updateComponent` を実行\\\n`state2.count` が変わったら → `onCountUpdated` を実行\\\nという関係を記録しています！\n\n</KawaikoNote>\n\nそこで登場する概念が `track` と `trigger` です．\nそれぞれ名前の通り，`track` は `TargetMap` に登録する関数，`trigger` は `TargetMap` から作用を取り出して実行する関数です．\n\n```ts\nexport function track(target: object, key: unknown) {\n  // ..\n}\n\nexport function trigger(target: object, key?: unknown) {\n  // ..\n}\n```\n\nそして，この track と trigger は Proxy の get と set のハンドラに実装されます．\n\n```ts\nconst state = new Proxy(\n  { count: 1 },\n  {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  },\n)\n```\n\nこの Proxy 生成のための API が reactive 関数です．\n\n```ts\nfunction reactive<T>(target: T) {\n  return new Proxy(target, {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n![reactive track and trigger flow](/figures/10-minimum-example/reactivity/reactive-track-trigger.svg)\n\n<KawaikoNote variant=\"base\" title=\"ここまでのポイント\">\n\n- **track**: 値を「読んだとき」に呼ばれ，「この値が変わったら○○を実行してね」と登録\n- **trigger**: 値を「書き換えたとき」に呼ばれ，登録された処理を実行\n- **reactive**: この仕組みを持った Proxy を作る関数\n\n</KawaikoNote>\n\nここで，一つ足りない要素について気づくかもしれません．それは「track ではどの関数を登録するの?」という点です．\n答えを言ってしまうと，これが `activeEffect` という概念です．\nこれは，targetMap と同様，このモジュール内のグローバル変数として定義されていて，ReactiveEffect の `run` というメソッドで随時設定されます．\n\n```ts\nlet activeEffect: ReactiveEffect | undefined\n\nclass ReactiveEffect {\n  constructor(\n    // ここに実際に作用させたい関数を持たせます。 (今回でいうと、updateComponent)\n    public fn: () => T,\n  ) {}\n\n  run() {\n    activeEffect = this\n    return this.fn()\n  }\n}\n```\n\nどのような原理かというと，このようなコンポーネントを想像してください．\n\n```ts\n{\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return function render() {\n      return h(\"div\", { id: \"my-app\" }, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\n          \"button\",\n          {\n            onClick: increment,\n          },\n          [\"increment\"]\n        ),\n      ]);\n    };\n  },\n}\n```\n\nこれを，内部的には以下のようにリアクティブを形成します．\n\n```ts\n// chibivue 内部実装\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    const effect = new ReactiveEffect(updateComponent)\n    effect.run()\n  },\n}\n```\n\n順を追って説明すると，まず，`setup` 関数が実行されます．\nこの時点で reactive proxy が生成されます．つまり，ここで作られた proxy に対してこれから何か操作があると proxy で設定した通り動作をとります．\n\n```ts\nconst state = reactive({ count: 0 }) // proxyの生成\n```\n\n次に，`updateComponent` を渡して `ReactiveEffect` (Observer 側)を生成します．\n\n```ts\nconst effect = new ReactiveEffect(updateComponent)\n```\n\nこの `updateComponent` で使っている `componentRender` は `setup` の`戻り値`の関数です．そしてこの関数は proxy によって作られたオブジェクトを参照しています．\n\n```ts\nfunction render() {\n  return h('div', { id: 'my-app' }, [\n    h('p', {}, [`count: ${state.count}`]), // proxy によって作られたオブジェクトを参照している\n    h(\n      'button',\n      {\n        onClick: increment,\n      },\n      ['increment'],\n    ),\n  ])\n}\n```\n\n実際にこの関数が走った時，`state.count` の `getter` 関数が実行され，`track` が実行されるようになっています．\nこの状況下で，effect を実行してみます．\n\n```ts\neffect.run()\n```\n\nそうすると，まず `activeEffect` に `updateComponent` (を持った ReactiveEffect) が設定されます．\nこの状態で `track` が走るので，`targetMap` に `state.count` と `updateComponent` (を持った ReactiveEffect) のマップが登録されます．\nこれがリアクティブの形成です．\n\nここで，increment が実行された時のことを考えてみましょう．\nincrement では `state.count` を書き換えているので `setter` が実行され，`trigger` が実行されます．\n`trigger` は `state` と `count` を元に `targetMap` から `effect`(今回の例だと updateComponent)をみつけ，実行します．\nこれで画面の更新が行われるようになりました!\n\nこれによって，リアクティブを実現することができます．\n\nちょっとややこしいので図でまとめます．\n\n![Reactivity setup flow during mount](/figures/10-minimum-example/reactivity/reactivity-setup-flow.svg)\n\n## これらを踏まえて実装しよう\n\n一番難しいところは上記までの理解なので，理解ができればあとはソースコードを書くだけです．\nとは言っても，実際のところどうなってるのかよく分からず上記だけでは理解ができない方もいるでしょう．\nそんな方も一旦ここで実装してみましょう．それから実際のコードを読みながら先ほどのセクションを見返してもらえたらと思います!\n\nまずは必要なファイルを作ります．`packages/reactivity`に作っていきます．\nここでも本家 Vue の構成をなるべく意識します．\n\n```sh\npwd # ~\nmkdir packages/reactivity\n\ntouch packages/reactivity/index.ts\n\ntouch packages/reactivity/dep.ts\ntouch packages/reactivity/effect.ts\ntouch packages/reactivity/reactive.ts\ntouch packages/reactivity/baseHandler.ts\n```\n\n例の如く，index.ts は export しているだけなので特に説明はしません．reactivity 外部パッケージから使いたくなったものはここから export しましょう．\n\ndep.ts からです．\n\n```ts\nimport { type ReactiveEffect } from './effect'\n\nexport type Dep = Set<ReactiveEffect>\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects)\n  return dep\n}\n```\n\neffect の定義がないですがこれから実装するので Ok です．\n\n続いて effect.ts です．\n\n```ts\nimport { Dep, createDep } from './dep'\n\ntype KeyToDepMap = Map<any, Dep>\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nexport let activeEffect: ReactiveEffect | undefined\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    // ※ fnを実行する前のactiveEffectを保持しておいて、実行が終わった後元に戻します。\n    // これをやらないと、どんどん上書きしてしまって、意図しない挙動をしてしまいます。(用が済んだら元に戻そう)\n    let parent: ReactiveEffect | undefined = activeEffect\n    activeEffect = this\n    const res = this.fn()\n    activeEffect = parent\n    return res\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target)\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()))\n  }\n\n  let dep = depsMap.get(key)\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()))\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect)\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  const dep = depsMap.get(key)\n\n  if (dep) {\n    const effects = [...dep]\n    for (const effect of effects) {\n      effect.run()\n    }\n  }\n}\n```\n\ntrack と trigger の中身についてこれまで解説していないのですが，単純に targetMap に登録をしたり取り出して実行したりしているだけなので頑張って読んでみてください．\n\n続いて baseHandler.ts です．ここには reactive proxy のハンドラを定義します．\nまあ，reactive に直接実装してもいいのですが，本家がこうなっているので真似してみました．\n実際には readonly や shallow などさまざまなプロキシが存在するのでそれらのハンドラをここに実装するイメージです．(今回はやりませんが)\n\n```ts\nimport { track, trigger } from './effect'\nimport { reactive } from './reactive'\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key)\n\n    const res = Reflect.get(target, key, receiver)\n    // objectの場合はreactiveにしてあげる (これにより、ネストしたオブジェクトもリアクティブにすることができます。)\n    if (res !== null && typeof res === 'object') {\n      return reactive(res)\n    }\n\n    return res\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key]\n    Reflect.set(target, key, value, receiver)\n    // 値が変わったかどうかをチェックしてあげておく\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key)\n    }\n    return true\n  },\n}\n\nconst hasChanged = (value: any, oldValue: any): boolean =>\n  !Object.is(value, oldValue)\n```\n\nここで，Reflect というものが登場していますが，Proxy と似た雰囲気のものなんですが，Proxy があるオブジェクトに対する設定を書き込む処理だったのに対し，Reflect はあるオブジェクトに対する処理を行うものです．\nProxy も Reflect も JS エンジン内のオブジェクトにまつわる処理の API で，普通にオブジェクトを使うのと比べてメタなプログラミングを行うことができます．\nそのオブジェクトを変化させる関数を実行したり，読み取る関数を実行したり，key が存在するのかをチェックしたりさまざまなメタ操作ができます．\nとりあえず，Proxy = オブジェクトを作る段階でのメタ設定， Reflect = 既に存在しているオブジェクトに対するメタ操作くらいの理解があれば OK です．\n\n続いて reactive.ts です．\n\n```ts\nimport { mutableHandlers } from './baseHandler'\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\nこれで reactive 部分の実装は終わりなので，mount する際に実際にこれらを使ってみましょう．\n`~/packages/runtime-core/apiCreateApp.ts`です．\n\n```ts\nimport { ReactiveEffect } from '../reactivity'\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const componentRender = rootComponent.setup!()\n\n        const updateComponent = () => {\n          const vnode = componentRender()\n          render(vnode, rootContainer)\n        }\n\n        // ここから\n        const effect = new ReactiveEffect(updateComponent)\n        effect.run()\n        // ここまで\n      },\n    }\n\n    return app\n  }\n}\n```\n\nさて，あとは playground で試してみましょう．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n![Reactive example mistake in the browser](/figures/10-minimum-example/reactivity/reactive-example-mistake.png)\n\nあっ………\n\nちゃんとレンダリングはされるようになりましたが何やら様子がおかしいです．\nまぁ，無理もなくて，`updateComponent`では毎回要素を作っています．\nなので，2 回目以降のレンダリングの際に古いものはそのままで，新しく要素が作られてしまっているのです．\nなので，レンダリング前に毎回要素を全て消してあげましょう．\n\n`~/packages/runtime-core/renderer.ts`の render 関数をいじります．\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild) // 全消し処理を追加\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\nさてこれでどうでしょう．\n\n![Reactive example rendered in the browser](/figures/10-minimum-example/reactivity/reactive-example-result.png)\n\n今度は大丈夫そうです!\n\nこれで reactive に画面を更新できるようになりました!!\n\n<KawaikoNote variant=\"surprise\" title=\"おめでとう！\">\n\nリアクティビティシステムの基本が完成しました！\\\nVue.js の「値を変えたら画面が更新される」魔法の正体，理解できましたか？\\\nここを乗り越えたあなたは，Vue.js の内部をかなり深く理解しています！\n\n</KawaikoNote>\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/030_reactive_system)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/040-minimum-virtual-dom.md",
    "content": "# 小さい仮想 DOM\n\n## 仮想 DOM，何に使われる?\n\n<KawaikoNote variant=\"question\" title=\"なぜ仮想 DOM？\">\n\n仮想 DOM の目的は「差分更新」です．\n変更があった部分だけを特定して，必要な DOM 操作だけを行います！\n（ただし仮想 DOM 自体のオーバーヘッドもあるので万能ではありません）\n\n</KawaikoNote>\n\n前のチャプターでリアクティビティシステムを導入したことで画面を動的に更新できるようになりました．\n改めて現在の render 関数の内容を見てみましょう．\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild)\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\n前のチャプターの時点で「これはヤバそうだ」と気づいた人ももしかしたらいるかもしれません．\nこの関数にはとんでもない無駄が存在します．\n\nplayground を見てみてください．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n```\n\n何がまずいかというと，increment を実行した時に変化する部分は，`count: ${state.count}` の部分だけなのに，renderVNode では一度全ての DOM を削除し，1 から再生成しているのです．  \nこれはなんとも無駄だらけな感じがしてなりません．今はまだ小さいので，これくらいでも特に問題なく動いているように見えますが，普段 Web アプリケーションを開発しているときような複雑な DOM を毎度毎度丸ごと作り替えるととんでもなくパフォーマンスが落ちてしまうのが容易に想像できると思います．  \nそこで，せっかく仮想 DOM を持っているわけですから，画面を描画する際に，前の仮想 DOM と比較して差分があったところだけを DOM 操作で書き換えるような実装をしたくなります．  \nさて，今回のメインテーマはこれです．\n\nやりたいことをソースコードベースで見てみましょう．\n上記のようなコンポーネントがあったとき，render 関数の戻り値は以下のような仮想 DOM になっています．\n初回のレンダリング時には count は 0 なので以下のようになります．\n\n```ts\nconst vnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 0`]\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      children: [\"increment\"]\n    }\n  ]\n}\n```\n\nこの vnode を持っておいて，次のレンダリングの時の vnode はまた別で持つことにしましょう．以下は 1 回目のボタンがクリックされた時の vnode です．\n\n```ts\nconst nextVnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 1`] // ここだけ更新したいなぁ〜\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      children: [\"increment\"]\n    }\n  ]\n}\n```\n\n今，vnode と，nextVnode の 2 つを持っている状態で，画面は vnode の状態です(nextVnode になる前)\nこれら二つを patch という関数に渡してあげて，差分だけレンダリングするようにしたいです．\n\n```ts\nconst vnode = {...}\nconst nextVnode = {...}\npatch(vnode, nextVnode, container)\n```\n\n先に関数名を紹介してしまいましたが，この差分レンダリングは「パッチ」と呼ばれます．差分検出処理 (reconciliation)と呼ばれることもあるようです．\nこのように 2 つの Virtual DOM を利用することで差分のみの画面更新を行うことができます．\n\n<KawaikoNote variant=\"funny\" title=\"パッチという名前\">\n\n「patch」は「継ぎ当て」という意味．破れた服を直すように，\n変わった部分だけを「継ぎ当て」して修正するイメージです！\n\n</KawaikoNote>\n\n## patch 関数の実装を行う前に\n\n本筋とは関係ないのですが，ちょっとここらで軽いリファクタを行なっておきます．(今から話すことにとって都合がいいので)\nvnode.ts に createVNode と言う関数を作っておいて，h 関数からはそれを呼ぶようにしておきましょう．\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: unknown,\n): VNode {\n  const vnode: VNode = { type, props, children }\n  return vnode\n}\n```\n\nh 関数も変更\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return createVNode(type, props, children)\n}\n```\n\nここからが本筋なのですが，これまで VNode が持つ子要素の型は`(Vnode | string)[]`としてきたのですが，Text を文字列として扱い続けるのもなんなので，VNode に統一してみようと思います．\nText も実際にはただの文字列ではなく，HTML の TextElement として存在するので，文字列以上の情報を含みます．それらの周辺情報を扱うためにも VNode として扱いたいわけです．\n具体的には，Text と言う Symbol を用いて，VNode の type として持たせましょう．\n例えば，`\"hello\"`のようなテキストがあった時，\n\n```ts\n{\n  type: Text,\n  props: null,\n  children: \"hello\"\n}\n```\n\nとしてあげる感じです．\n\nまた，ここで一つ注意点なのは，h 関数を実行した時は従来通りの表現をして，上記のように Text を表現するのは，render 関数内で normalize と言う関数を噛ませて変換することにします．  \nこれは本家の Vue.js に合わせてそうすることにします．\n\n`~/packages/runtime-core/vnode.ts`;\n\n```ts\nexport const Text = Symbol();\n\nexport type VNodeTypes = string | typeof Text;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\n// normalize後の型\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(..){..} // 省略\n\n// normalize 関数を実装。(renderer.tsで使う)\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    // stringだった場合に先ほど紹介した扱いたい形に変換する\n    return createVNode(Text, null, String(child));\n  }\n}\n```\n\nこれで Text も VNode として扱えるようになりました．\n\n## patch 関数の設計\n\nまず，patch 関数の設計をコードベースで見てみましょう．(実装フェーズはまた別であるのでここではまだ実装しなくていいです．理解だけ．)  \npatch 関数でやりたいことは 2 つの vnode の比較なので，便宜上それぞれ vnode1, vnode2 とするのですが，初回は vnode1 がありません．  \nつまり，patch 関数での処理は「初回(vnode2 から dom を生成)」と，「vnode1 と vnode2 の差分を更新」の処理に分かれます．  \nこれらの処理をそれぞれ「mount」と「patch」と名付けることにします．  \nそしてそれらは ElementNode と TextNode それぞれで行うようにします．\\\nそれぞれの mount と patch を process と言う名前でまとめてます．\n\n<img   \n    src=\"/figures/10-minimum-example/virtual-dom/patch-function-architecture.svg\"\n    alt=\"Patch Function Architecture\"   \n    style=\"background-color: white;\"\n/>\n\n```ts\nconst patch = (\n  n1: VNode | string | null,\n  n2: VNode | string,\n  container: HostElement,\n) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else {\n    processElement(n1, n2, container)\n  }\n}\n\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: HostElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    patchElement(n1, n2)\n  }\n}\n\nconst processText = (n1: string | null, n2: string, container: HostElement) => {\n  if (n1 == null) {\n    mountText(n2, container)\n  } else {\n    patchText(n1, n2)\n  }\n}\n```\n\n## 実際に実装してみる\n\nここから実際に仮想 DOM の patch を実装していきます．  \nまず，Element にしろ，Text にしろ，マウントした段階で vnode に実際の DOM への参照を持たせておきたいので，vnode の el というプロパティを持たせておきます．\n\n`~/packages/runtime-core/vnode.ts`\n\n```ts\nexport interface VNode<HostNode = RendererNode> {\n  type: VNodeTypes\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined // [!code ++]\n}\n```\n\nそれではここからは `~/packages/runtime-core/renderer.ts` です．  \ncreateRenderer 関数の中に実装していきましょう．\nrenderVNode 関数は消してしまいます．\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  // .\n  // .\n  // .\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2\n    if (type === Text) {\n      // processText(n1, n2, container);\n    } else {\n      // processElement(n1, n2, container);\n    }\n  }\n}\n```\n\nprocessElement の mountElement から実装していきます．\n\n```ts\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    // patchElement(n1, n2);\n  }\n}\n\nconst mountElement = (vnode: VNode, container: RendererElement) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children, el) // TODO:\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\n要素なので，当然子要素のマウントも必要です．\nここで，先ほど作った normalize 関数を噛ませておきましょう．\n\n```ts\nconst mountChildren = (children: VNode[], container: RendererElement) => {\n  for (let i = 0; i < children.length; i++) {\n    const child = (children[i] = normalizeVNode(children[i]))\n    patch(null, child, container)\n  }\n}\n```\n\nここまでで要素のマウントは実装できました．  \n次は Text のマウントからやりましょう．と，言ってもこちらはただ DOM 操作をするだけです．  \n設計の説明では mountText と patchText のように関数に分けてましたが，たいした処理もなく，今後もそれほど複雑にならないはずなので直接書いてしまいます．\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // TODO: patch\n  }\n}\n```\n\n一旦ここまでで，初回のマウントはできるようになったはずなので，render 関数で patch 関数を使用して playground で試してみましょう!  \n今まで，createAppAPI の mount に書いていた処理を一部 render 関数に移植して，２つの vnode を保持できるようにします．  \n具体的には．render 関数に rootComponent を渡して，その中で ReactiveEffect の登録等を行うように変更します．\n\n```ts\nreturn function createApp(rootComponent) {\n  const app: App = {\n    mount(rootContainer: HostElement) {\n      // rootComponentを渡すだけに\n      render(rootComponent, rootContainer)\n    },\n  }\n}\n```\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\nここまでできたら playground で描画できるかどうか試してみましょう！\n\nまだ，patch の処理は行なっていないので画面の更新は行われません．\n\nと，言うことで引き続き patch の処理を書いていきましょう．\n\n```ts\nconst patchElement = (n1: VNode, n2: VNode) => {\n  const el = (n2.el = n1.el!)\n\n  const props = n2.props\n\n  patchChildren(n1, n2, el)\n\n  for (const key in props) {\n    if (props[key] !== n1.props?.[key]) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n}\n\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\nText も同様に．\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // patchの処理を追加\n    const el = (n2.el = n1.el!)\n    if (n2.children !== n1.children) {\n      hostSetText(el, n2.children as string)\n    }\n  }\n}\n```\n\n※ patchChildren に関して，本来は key 属性などを付与して動的な長さの子要素に対応したりしないといけないのですが，今回は小さく仮想 DOM を実装するのでその辺の実用性については触れません．  \nそのあたりをやりたい方は Basic Virtual DOM 部門で説明するのでぜひそちらをご覧ください．ここでは仮想 DOM の実装雰囲気であったり，役割が理解できるところまでの理解を目指します．\n\nさて，これで差分レンダリングができるようになったので，playground を見てみましょう．\n\n![patch rendering result in the browser](/figures/10-minimum-example/virtual-dom/patch-rendering-result.png)\n\nこれで仮想 DOM を利用したパッチが実装できました!!!!! 祝\n\n<KawaikoNote variant=\"surprise\" title=\"仮想 DOM 完成！\">\n\n差分検出の仕組みが実装できました！これがフレームワークのコア技術です．\nたった数百行で「フレームワークの心臓部」が動いているのを実感してください！\n\n</KawaikoNote>\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/040_vdom_system)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/050-minimum-component.md",
    "content": "# コンポーネント指向で開発したい\n\n<KawaikoNote variant=\"question\" title=\"なぜコンポーネント？\">\n\nコンポーネントは UI を再利用可能な部品に分割する仕組みです．\n同じボタンを何度も書く代わりに，一度定義して使い回せます！\n\n</KawaikoNote>\n\n## 既存実装の整理ベースで考える\n\nこれまで，createApp API やリアクティビティシステム， 仮想 DOM を小さく実装してきました．  \n今現時点での実装ではリアクティビティシステムによって UI を動的に変更することもできますし，仮想 DOM によって差分レンダリングを行うことができているのですが，開発者インタフェースとしては全ての内容を createApp API に書く感じになってしまっています．  \n実際にはもっとファイルを分割したり，再利用のために汎用的なコンポーネントを実装したいです．  \nまずは既存実装の散らかってしまっている部分を見直してみます．renderer.ts の render 関数をみてください．\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n  let n2: VNode = null!\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\nrender 関数内にルートコンポーネントに関する情報を直接定義してしまっています．  \n実際には，n1 や n2, updateComponent, effect は各コンポーネントごとに存在します．  \n実際，これからはユーザー側でコンポーネント(ある意味でコンストラクタ)を定義してそれをインスタンス化したいわけです．  \nそして，そのインスタンスが n1 や n2, updateComponent などを持つような感じにしたいです．  \nそこで，コンポーネントのインスタンスとしてこれらを閉じ込めることについて考えてみます．\n\n`~/packages/runtime-core/component.ts` に `ComponentInternalInstance`と言うものを定義してみます．\nこれがインスタンスの型となります．\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component // 元となるユーザー定義のコンポーネント (旧 rootComponent (実際にはルートコンポーネントだけじゃないけど))\n  vnode: VNode // 後述\n  subTree: VNode // 旧 n1\n  next: VNode | null // 旧 n2\n  effect: ReactiveEffect // 旧 effect\n  render: InternalRenderFunction // 旧 componentRender\n  update: () => void // 旧updateComponent\n  isMounted: boolean\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild\n}\n```\n\nこのインスタンスが持つ vnode と subTree と next は少しややこしいのですが，\nこれから，VNode の type として ConcreteComponent を指定できるように実装するのですが，instance.vnode にはその VNode 自体を保持しておきます．\nそして，subTree, next というのはそのコンポーネントのレンダリング結果である VNode を保持させます．(ここは今までの n1 と n2 と変わらない)\n\nイメージ的には，\n\n```ts\nconst MyComponent = {\n  setup() {\n    return h('p', {}, ['hello'])\n  },\n}\n\nconst App = {\n  setup() {\n    return h(MyComponent, {}, [])\n  },\n}\n```\n\nのように利用し，  \nMyComponent のインスタンスを instance とすると，instance.vnode には `h(MyComponent, {}, [])` の結果が，instance.subTree には `h(\"p\", {}, [\"hello\"])` の結果が格納される感じです．\n\nとりあえず，h 関数の第一引数にコンポーネントを指定できるように実装してみましょう．  \nと，言ってもただ単に type としてコンポーネント定義のオブジェクトを受け取るようにするだけです．  \n`~/packages/runtime-core/vnode.ts`\n\n```ts\nexport type VNodeTypes = string | typeof Text | object // objectを追加;\n```\n\n`~/packages/runtime-core/h.ts`\n\n```ts\nexport function h(\n  type: string | object, // objectを追加\n  props: VNodeProps\n) {..}\n```\n\nVNode に component のインスタンスを持たせるようにもしておきます．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  component: ComponentInternalInstance | null // 追加\n}\n```\n\nそれに伴って，renderer の方でもコンポーネントを扱う必要が出てくるのですが，Element や Text と同様 processComponent を実装して，mountComponent と patchComponent (updateComponent) も実装していきましょう．\n\nまずガワから作って詳細な説明をします．\n\n![Component instance flow](/figures/10-minimum-example/minimum-component/component-instance-flow.svg)\n\n```ts\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    // 分岐を追加\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountComponent(n2, container)\n  } else {\n    updateComponent(n1, n2)\n  }\n}\n\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // TODO:\n}\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  // TODO:\n}\n```\n\nでは，mountComponent から見てみましょう．  \nやることは 3 つです．\n\n1. コンポーネントのインスタンスを生成\n2. setup の実行とその結果をインスタンスに保持\n3. ReactiveEffect の生成とそれをインスタンスに保持\n\nまず，component.ts にコンポーネントのインスタンスを生成するための関数(コンストラクタの役割をするもの)を実装してみます．\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    type,\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n    isMounted: false,\n  }\n\n  return instance\n}\n```\n\n各プロパティの型は non-null なのですが，インスタンスを生成した段階では null で入れてしまいます．(本家の Vue.js に合わせてこのような設計にしています．)\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n  // TODO: setup component\n  // TODO: setup effect\n}\n```\n\n続いて setup です．これは今まで render に直接書いていた処理をここで行うようにして，変数ではなくインスタンスに保持させてしまえば OK です．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  // TODO: setup effect\n}\n```\n\n最後に，effect の形成なのですが，少し長くなりそうなので setupRenderEffect という関数にまとめてしまいます．  \nここに関しても，やるべきことは基本的に今まで render 関数に直接実装していたものをインスタンスの状態を活用しつつ移植するだけです．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render } = instance\n\n    if (!instance.isMounted) {\n      // mount process\n      const subTree = (instance.subTree = normalizeVNode(render()))\n      patch(null, subTree, container)\n      initialVNode.el = subTree.el\n      instance.isMounted = true\n    } else {\n      // patch process\n      let { next, vnode } = instance\n\n      if (next) {\n        next.el = vnode.el\n        next.component = instance\n        instance.vnode = next\n        instance.next = null\n      } else {\n        next = vnode\n      }\n\n      const prevTree = instance.subTree\n      const nextTree = normalizeVNode(render())\n      instance.subTree = nextTree\n\n      patch(prevTree, nextTree, hostParentNode(prevTree.el!)!) // ※ 1\n      next.el = nextTree.el\n    }\n  }\n\n  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn))\n  const update = (instance.update = () => effect.run()) // instance.updateに登録\n  update()\n}\n```\n\n※ 1: nodeOps に親 Node を取得するための `parentNode` という関数を実装してください．\n\n```ts\nparentNode: (node) => {\n    return node.parentNode;\n},\n```\n\n多少長いですが，特に難しいことはないかと思います．\nsetupRenderEffect でインスタンスの update メソッドとして更新のための関数を登録してあるので，updateComponent ではそれを呼んであげるだけです．\n\n```ts\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!\n  instance.next = n2\n  instance.update()\n}\n```\n\n最後に，今まで render 関数に定義していた実装は不要になるので消してしまいます．\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  patch(null, vnode, container)\n}\n```\n\nこれで Component をレンダリングすることができました．試しに playground コンポーネントを作ってみてみましょう．\nこのように，コンポーネントに分割してレンダリングができるようになっているかと思います．\n\n<KawaikoNote variant=\"funny\" title=\"インスタンスの役割\">\n\nコンポーネントの「設計図」と「インスタンス」は別物です．\n同じ CounterComponent を 3 つ並べても，それぞれが独立した状態を持てます！\n\n</KawaikoNote>\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst CounterComponent = {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n<KawaikoNote variant=\"surprise\" title=\"コンポーネントシステム完成！\">\n\nこれで UI を部品化できるようになりました！\n実際のアプリ開発でも，この仕組みが大活躍します．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system)\n\n\n\n\n\n\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/051-component-props.md",
    "content": "# Props の実装\n\n## 開発者インターフェース\n\nまずは props から実装していきます．  \n最終的な開発者インタフェースから考えてみましょう．  \nprops は setup 関数の第一引数として渡ってくるようなものを考えてみます．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\n## 実装\n\nこれを元に ComponentInternalInstance に持たせたい情報を考えてみます．\\\n`props: { message: { type: String } }` のように指定された props の定義と，props の値を実際に保持するプロパティが必要なので以下のように追加します．\n\n```ts\nexport type Data = Record<string, unknown>\n\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  propsOptions: Props // `props: { message: { type: String } }` のようなオブジェクトを保持\n\n  props: Data // 実際に親から渡されたデータを保持 (今回の場合、 `{ message: \"hello\" }` のような感じになる)\n}\n```\n\n`~/packages/runtime-core/componentProps.ts` というファイルを以下の内容で新たに作成します．\n\n```ts\nexport type Props = Record<string, PropOptions | null>\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null\n  required?: boolean\n  default?: null | undefined | object\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} }\n```\n\nユーザーがコンポーネントを実装する際のオプションにも追加します．\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any> // 追加\n  setup?: () => Function\n  render?: Function\n}\n```\n\nオプションから渡された props の定義を createComponentInstance でインスタンスを生成する際に propsOptions にセットします．\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    propsOptions: type.props || {},\n    props: {},\n```\n\n肝心の instance.props をどう形成するかというと，コンポーネントのマウント時に vnode が保持している props を propsOptions を元にフィルターします．\\\nフィルターしてできたオブジェクトを reactive 関数によってリアクティブなオブジェクトにし，instance.prop にセットします．\n\nこの一連の流れを実装する`initProps`という関数を componentProps.ts に実装します．\n\n```ts\nexport function initProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const props: Data = {}\n  setFullProps(instance, rawProps, props)\n  instance.props = reactive(props)\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      if (options && options.hasOwnProperty(key)) {\n        props[key] = value\n      }\n    }\n  }\n}\n```\n\n実際に mount 時に initProps を実行し，setup 関数の引数に props を渡してみましょう．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    // init props\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup(\n        instance.props // setupに渡す\n      ) as InternalRenderFunction;\n    }\n    // .\n    // .\n    // .\n}\n```\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (props: Record<string, any>) => Function // propsを受け取るように\n  render?: Function\n}\n```\n\nこの時点で props を子コンポーネントに渡せるようになっているはずなので playground で確認してみましょう．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\nしかし，実はこれだけでは不十分で，props を変更した際に描画が更新されません．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```\n\nこのようなコンポーネントを動作させるために，componentProps.ts に `updateProps` を実装し，コンポーネントが update する際に実行してあげます．\n\n`~/packages/runtime-core/componentProps.ts`\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  Object.assign(props, rawProps)\n}\n```\n\nここで，コンポーネントの更新処理の流れを整理しておきましょう．\\\n親コンポーネントが再レンダリングされると，子コンポーネントに渡される props も変わる可能性があります．\\\nこの時の流れは以下のようになります：\n\n1. 親コンポーネントの `render` 関数が実行され，子コンポーネントの新しい VNode が生成される\n2. `patch` 処理で `processComponent` が呼ばれ，既存のコンポーネント（`n1`）と新しい VNode（`n2`）の比較が行われる\n3. 既存のコンポーネントが存在する場合は `updateComponent` 関数が呼ばれる\n\nまず，`ComponentInternalInstance` に `next` プロパティを追加します．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  vnode: VNode // 現在のVNode\n  next: VNode | null // 親からの更新要求があった場合に、新しいVNodeがここにセットされる\n  // .\n  // .\n}\n```\n\n次に，`processComponent` で既にマウントされているコンポーネントの更新処理を実装します．\n\n```ts\nconst processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  if (n1 == null) {\n    mountComponent(n2, container);\n  } else {\n    updateComponent(n1, n2); // 追加\n  }\n};\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!; // 古いVNodeから新しいVNodeにインスタンスの参照を引き継ぐ\n  instance.next = n2; // 新しいVNodeをnextにセット\n  instance.update(); // コンポーネントの更新をトリガー\n};\n```\n\n`updateComponent` では，新しい VNode (`n2`) を `instance.next` にセットしてから `instance.update()` を呼び出します．\\\nこれにより `componentUpdateFn` が実行されます．\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          // 親からの更新要求がある場合（props が変更された場合など）\n          next.el = vnode.el; // 新しいVNodeに現在のDOM要素への参照を引き継ぐ\n          next.component = instance; // 新しいVNodeにインスタンスへの参照をセット\n          instance.vnode = next; // インスタンスの「現在のVNode」を新しいものに切り替える\n          instance.next = null; // 処理済みなのでnullにリセット\n          updateProps(instance, next.props); // 新しいpropsでインスタンスのpropsを更新\n        }\n        // nextがない場合は、コンポーネント自身のリアクティブな状態変更による更新\n```\n\n`instance.next` が存在する場合，それは親コンポーネントからの更新要求（props の変更など）があったことを意味します．\\\nこの場合，新しい VNode の情報をインスタンスに反映させてから，props を更新します．\\\n`instance.next` が存在しない場合は，コンポーネント自身の内部状態（リアクティブな値）の変更による再レンダリングです．\n\nこれで画面が更新されるようになれば OK です．\\\nこれで props を利用することによってコンポーネントにデータを受け渡せるようになりました！　やったね！\n\n![Component props flow in the browser](/figures/10-minimum-example/component-props/props-flow.png)\n\nここまでのソースコード：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system2)\n\nついでと言ってはなんなのですが，本家 Vue は props をケバブケースで受け取ることができるのでこれも実装してみましょう．\\\nここで，新たに `~/packages/shared` というディレクトリを作成し， `general.ts` を作成します．\\\nここは，runtime-core や runtime-dom に限らず，汎用的な関数を定義する場所です．\\\nこのタイミングで作る意味というのは特別ないのですが，本家に倣ってついでに作っておきます．\\\nそして，今回は `hasOwn` と `camelize` を実装してみます．\n\n`~/packages/shared/general.ts`\n\n```ts\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nexport const hasOwn = (\n  val: object,\n  key: string | symbol,\n): key is keyof typeof val => hasOwnProperty.call(val, key)\n\nconst camelizeRE = /-(\\w)/g\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))\n}\n```\n\ncomponentProps.ts で camelize してあげましょう．\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  // -------------------------------------------------------------- ここ\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value\n  })\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      // -------------------------------------------------------------- ここ\n      // kebab -> camel\n      let camelKey\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value\n      }\n    }\n  }\n}\n```\n\nこれでケバブケースを扱うこともできるようになったはずです． playground で確認してみましょう．\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: { someMessage: string }) {\n    return () => h('div', {}, [`someMessage: ${props.someMessage}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { 'some-message': state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/052-component-emits.md",
    "content": "# Emit の実装\n\n## 開発者インターフェース\n\nprops に引き続き emit の実装をしていきます．\\\nemit の実装は比較的ライトなのですぐに終わります．\n\n開発者インタフェース的には emit は setup 関数の第 2 引数から受け取れるような形にします．\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n## 実装\n\nprops の時と同じように，`~/packages/runtime-core/componentEmits.ts` というファイルを作成してそこに実装していきます．\\\nemit は単純に，instance に emit 用の関数を実装し，実行時は vnode が持つ props からハンドラを探し実行します．\n\n`~/packages/runtime-core/componentEmits.ts`\n\n```ts\nexport function emit(\n  instance: ComponentInternalInstance,\n  event: string,\n  ...rawArgs: any[]\n) {\n  const props = instance.vnode.props || {}\n  let args = rawArgs\n\n  let handler =\n    props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]\n\n  if (handler) handler(...args)\n}\n```\n\n`~/packages/shared/general.ts`\n\n```ts\nexport const capitalize = (str: string) =>\n  str.charAt(0).toUpperCase() + str.slice(1)\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``)\n```\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  emit: (event: string, ...args: any[]) => void\n}\n\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    emit: null!, // to be set immediately\n  }\n\n  instance.emit = emit.bind(null, instance)\n  return instance\n}\n```\n\nこれを setup 関数に渡してあげれば OK です．\n\n`~/packages/runtime-core/componentOptions.ts`\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function // ctx.emitを受け取れるように\n  render?: Function\n}\n```\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      // emitを渡してあげる\n      instance.render = component.setup(instance.props, {\n        emit: instance.emit,\n      }) as InternalRenderFunction;\n    }\n```\n\n先ほど想定していた開発者インタフェースの例で動作を確認してみましょう！\\\nちゃんと動いていればこれで props/emit によるコンポーネント間のやりとりが行えるようになりました！\n\nここまでのソースコード：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system3)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/060-template-compiler.md",
    "content": "# テンプレートコンパイラ\n\n<KawaikoNote variant=\"question\" title=\"コンパイラって何？\">\n\n「コンパイラ」は翻訳機です．人間が書きやすい形式（template）から，\n機械が実行しやすい形式（h 関数）に変換します！\n\n</KawaikoNote>\n\n## 実はここまでで動作に必要なものは揃った ( ? )\n\nこれまで，リアクティビティシステムや仮想 DOM，コンポーネントなどを実装してきました．  \nこれらは非常に小さなもので，実用的なものではないのですが，実は動作に必要な構成要素の全体像としては一通り理解できたと言っても過言ではないのです．  \nそれぞれの要素自体の機能は足りていないですが，浅〜〜〜〜〜く 1 周した感じです．\n\nこのチャプターからはより Vue.js に近づけるためにテンプレートの機能を実装するのですが，これらはあくまで DX の改善のためのものであり，ランタイムに影響を出すものではありません．(厳密にはコンパイラ最適化により影響はでますが，本筋ではないので出ない，としておきます)  \nもう少し具体的にいうと，DX の向上のために開発者インタフェースを拡張し，「最終的には今まで作った内部実装に変換」します．\n\n## 今回実現したい開発者インタフェース\n\n今現時点ではこのような開発者インタフェースになっています．\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n現状だと，View の部分は h 関数を使って構築しています．より生の HTML に近づけるために template オプションに template を描けるようにしたいです．\\\nとは言っても，いきなり色々モリモリで実装するのは大変なので，少し機能を絞って作ってみます．\\\nとりあえず，以下のようなタスクに分割してやっていきます．\n\n1. 単純なタグとメッセージ，静的な属性を描画できるように\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n2. もう少し複雑な HTML を描画できるように\n\n```ts\nconst app = createApp({\n  template: `\n    <div>\n      <p>hello</p>\n      <button> click me! </button>\n    </div>\n  `,\n})\n```\n\n3. setup 関数で定義したものを使えるようにしたい\n\n```ts\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n\n    return { count, increment }\n  },\n\n  template: `\n    <div>\n      <p>count: {{ count }}</p>\n      <button v-on:click=\"increment\"> click me! </button>\n    </div>\n  `,\n})\n```\n\nそれぞれでさらに小さく分割はしていくのですが，おおまかにこの 3 ステップに分割してみます．  \nまずは 1 からやっていきましょう．\n\n## コンパイラがやっていること\n\nさて，今回目指す開発者インタフェースは以下のようなものです．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\nここでまず，コンパイラとはいったいなんなのかという話だけしておきます．  \nソフトウェアを書いているとたちまち「コンパイラ」という言葉を耳にするかと思います．  \n「コンパイル」というのは翻訳という意味で，ソフトウェアの領域だとより高級な記述から低級な記述へ変換する際によくこの言葉を使います．\\\nこの本の最初の方のこの言葉を覚えているでしょうか?\n\n> ここでは便宜上、生の JS に近ければ近いほど「低級な開発者インタフェース」と呼ぶことにします。  \n> そして、ここで重要なのが、「実装を始めるときは低級なところから実装していく」ということです。  \n> それはなぜかというと、多くの場合、高級な記述は低級な記述に変換されて動いているからです。  \n> つまり、1 も 2 も最終的には内部的に 3 の形に変換しているのです。  \n> その変換の実装のことを「コンパイラ (翻訳機)」と呼んでいます。\n\nでは，このコンパイラというものがなぜ必要なのかということについてですが，それは「開発体験を向上させる」というのが大きな目的の一つです．  \n最低限，動作するような低級なインタフェースが備わっていれば，機能としてはそれらだけで開発を進めることは可能です．  \nですが，記述がわかりづらかったり，機能に関係のない部分を考慮する必要が出てきたりと色々と面倒な問題がでてくるのはしんどいので，利用者の気持ちを考えてインタフェースの部分だけを再開発します．\n\nこの点で，Vue.js が目指している点は，「生の HTML のように書けかつ，Vue が提供する機能(ディレクティブなど)を活用して便利に View を書く」と言ったところでしょうか．\nそして，そこの行き着く先が SFC といったところでしょうか．\\\n昨今では jsx/tsx の流行もあり，Vue はもちろんこれらも開発者インタフェースの選択肢として提供しています．が，今回は Vue 独自の template を実装する方向でやってみようと思います．\n\n長々と，文章で説明してしまいましたが，結局今回やりたいことは，\n\nこのようなコードを，\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\nこのように翻訳(コンパイル)する機能を実装したいです．\n\n```ts\nconst app = createApp({\n  render() {\n    return h('p', { class: 'hello' }, ['Hello World'])\n  },\n})\n```\n\nもう少しスコープを狭めるなら，この部分です．\n\n```ts\n`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\n<KawaikoNote variant=\"funny\" title=\"テンプレートの正体\">\n\ntemplate は最終的に h 関数の呼び出しに変換されます．\nつまり，今まで実装した h 関数がここでも活躍するわけです！\n\n</KawaikoNote>\n\nいくつかのフェーズに分けて，段階的に実装を進めていきましょう．\n\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/061-template-compiler-impl.md",
    "content": "# テンプレートコンパイラを実装する\n\n## 実装アプローチ\n\n基本的なアプローチとしては，template オプションで渡された文字列を操作して特定の関数を生成する感じです．  \nコンパイラを３つの要素に分割してみます．\n\n### 解析\n\n解析 (parse) は渡された文字列から必要な情報を解析します．\\\n以下のようなイメージをしてもらえれば OK です．\n\n```ts\nconst { tag, props, textContent } = parse(`<p class=\"hello\">Hello World</p>`)\nconsole.log(tag) // \"p\"\nconsole.log(prop) // { class: \"hello\" }\nconsole.log(textContent) // \"Hello World\"\n```\n\n### コード生成\n\nコード生成(codegen)では parse の結果をもとにコード(文字列)を生成します．\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"h('p', { class: 'hello' }, ['Hello World']);\"\n```\n\n### 関数オブジェクト生成\n\ncodegen で生成したコード(文字列)をもとに実際に実行可能な関数を生成します．\\\nJavaScript では，Function コンストラクタを利用することで文字列から関数を生成することが可能です．\n\n```ts\nconst f = new Function('return 1')\nconsole.log(f()) // 1\n\n// 引数を定義する場合はこんな感じ\nconst add = new Function('a', 'b', 'return a + b')\nconsole.log(add(1, 1)) // 2\n```\n\nこれを利用して関数を生成します．\\\nここで一点注意点があるのですが，生成した関数はその中で定義された変数しか扱うことができないので，h 関数などの読み込みもこれに含んであげます．\n\n```ts\nimport * as runtimeDom from './runtime-dom'\nconst render = new Function('ChibiVue', code)(runtimeDom)\n```\n\nこうすると，ChibiVue という名前で runtimeDom を受け取ることができるので，codegen の段階で以下のように h 関数を読み込めるようにしておきます．\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"return () => { const { h } = ChibiVue; return h('p', { class: 'hello' }, ['Hello World']); }\"\n```\n\nつまり，先ほど，\n\n```ts\n`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\nのように変換すると言いましたが，正確には，\n\n```ts\n`<p class=\"hello\">Hello World</p>`\n\n// ↓\n\nChibiVue => {\n  return () => {\n    const { h } = ChibiVue\n    return h('p', { class: 'hello' }, ['Hello World'])\n  }\n}\n```\n\nのように変換し，runtimeDom を渡して render 関数を生成します．\\\nそして，codegen の責務は\n\n```ts\nconst code = `\n  return () => {\n      const { h } = ChibiVue;\n      return h(\"p\", { class: \"hello\" }, [\"Hello World\"]);\n  };\n`\n```\n\nという文字列を生成することです．\n\n## 実装\n\nアプローチが理解できたら早速実装してみましょう．`~/packages` に `compiler-core` というディレクトリを作ってそこに `index.ts`, `parse.ts`, `codegen.ts` を作成します．\n\n```sh\npwd # ~/\nmkdir packages/compiler-core\ntouch packages/compiler-core/index.ts\ntouch packages/compiler-core/parse.ts\ntouch packages/compiler-core/codegen.ts\n```\n\nindex.ts は例の如く export するためだけに利用します．\n\nそれでは parse から実装していきましょう．\\\n`packages/compiler-core/parse.ts`\n\n```ts\nexport const baseParse = (\n  content: string,\n): { tag: string; props: Record<string, string>; textContent: string } => {\n  const matched = content.match(/<(\\w+)\\s+([^>]*)>([^<]*)<\\/\\1>/)\n  if (!matched) return { tag: '', props: {}, textContent: '' }\n\n  const [_, tag, attrs, textContent] = matched\n\n  const props: Record<string, string> = {}\n  attrs.replace(/(\\w+)=[\"']([^\"']*)[\"']/g, (_, key: string, value: string) => {\n    props[key] = value\n    return ''\n  })\n\n  return { tag, props, textContent }\n}\n```\n\n正規表現を使った非常に簡素なパーサではありますが，初めての実装としては十分です．\n\n続いて，コードの生成です．codegen.ts に実装していきます．\\\n`packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = ({\n  tag,\n  props,\n  textContent,\n}: {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}): string => {\n  return `return () => {\n  const { h } = ChibiVue;\n  return h(\"${tag}\", { ${Object.entries(props)\n    .map(([k, v]) => `${k}: \"${v}\"`)\n    .join(', ')} }, [\"${textContent}\"]);\n}`\n}\n```\n\nそれでは，これらを組み合わせて template から関数の文字列を生成する関数を実装します．`packages/compiler-core/compile.ts` というファイルを新たに作成します．\\\n`packages/compiler-core/compile.ts`\n\n```ts\nimport { generate } from './codegen'\nimport { baseParse } from './parse'\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template)\n  const code = generate(parseResult)\n  return code\n}\n```\n\n特に難しくないかと思います．実は，compiler-core の責務はここまでです．\n\n## ランタイム上のコンパイラとビルドプロセスのコンパイラ\n\n実は Vue にはコンパイラが 2 種類存在しています．  \nそれは，ランタイム上(ブラウザ上)で実行されるものと，ビルドプロセス上(Node.js など)で実行されるものです．  \n具体的には，ランタイムの方は template オプションまたは html として与えられるテンプレートのコンパイラ，ビルドプロセス上は SFC(や jsx)のコンパイラです．  \ntemplate オプションとはちょうど今我々が実装しているものです．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\nhtml として与えられるテンプレートというのは html に Vue の template を書くような開発者インタフェースです．(CDN 経由などでサクッと HTML に盛り込むのに便利です．)\n\n```ts\nconst app = createApp()\napp.mount('#app')\n```\n\n```html\n<div id=\"app\">\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert('hello')\">click me!</button>\n</div>\n```\n\nこれら 2 つはどちらも template をコンパイルする必要がありますが，コンパイルはブラウザ上で実行されます．\n\n一方で，SFC のコンパイルはプロジェクトのビルド時に行われ，ランタイム上にはコンパイル後のコードしか存在していません．(開発環境に vite や webpack 等のバンドラを用意する必要があります．)\n\n```vue\n<!-- App.vue -->\n<script>\nexport default {}\n</script>\n\n<template>\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert(\"hello\")\">click me!</button>\n</template>\n```\n\n```ts\nimport App from 'App.vue'\nconst app = createApp(App)\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\nそして，注目するべき点はどっちのコンパイラにせよ，共通の処理という点です．  \nこの共通部分のソースコードを実装しているのが `compiler-core` ディレクトリです．  \nそして，ランタイム上のコンパイラ，SFC コンパイラはそれぞれ`compiler-dom`, `compiler-sfc`というディレクトリに実装されています．  \nぜひ，ここらでこの図を見返してみてください．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n## 実装の続き\n\n少し話が飛んでしまいましたが，実装の続きをやっていきましょう．\\\n先ほどの話を考慮すると，今作っているのはランタイム上で動作するコンパイラなので，`compiler-dom`を作っていくのが良さそうです．\n\n```sh\npwd # ~/\nmkdir packages/compiler-dom\ntouch packages/compiler-dom/index.ts\n```\n\n`packages/compiler-dom/index.ts`に実装します．\n\n```ts\nimport { baseCompile } from '../compiler-core'\n\nexport function compile(template: string) {\n  return baseCompile(template)\n}\n```\n\n「えっっっっ，これじゃあただ codegen しただけじゃん．関数の生成はどうするの？」と思ったかも知れません．  \n実はここでも関数の生成は行なっておらず，どこで行うかというと`packages/index.ts`です．(本家のコードで言うと [packages/vue/src/index.ts](https://github.com/vuejs/core/blob/main/packages/vue/src/index.ts) です)\n\n`packages/index.ts`を実装したいところですが，ちょいと下準備があるので先にそちらからやります．\\\nその下準備というのは，`packages/runtime-core/component.ts`にコンパイラ本体を保持する変数と，登録用の関数の実装です．\n\n`packages/runtime-core/component.ts`\n\n```ts\ntype CompileFunction = (template: string) => InternalRenderFunction\nlet compile: CompileFunction | undefined\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile\n}\n```\n\nそれでは，`packages/index.ts`で関数の生成をして，登録してあげましょう．\n\n```ts\nimport { compile } from './compiler-dom'\nimport { InternalRenderFunction, registerRuntimeCompiler } from './runtime-core'\nimport * as runtimeDom from './runtime-dom'\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template)\n  return new Function('ChibiVue', code)(runtimeDom)\n}\n\nregisterRuntimeCompiler(compileToFunction)\n\nexport * from './runtime-core'\nexport * from './runtime-dom'\nexport * from './reactivity'\n```\n\n※ runtimeDom には h 関数を含める必要があるので `runtime-dom` で export するのを忘れないようにしてください．\n\n```ts\nexport { h } from '../runtime-core'\n```\n\nさて，コンパイラの登録ができたので実際にコンパイルを実行したいです．\\\nコンポーネントのオプションの型に template がなくては始まらないのでとりあえず template は生やしておきます．\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function\n  render?: Function\n  template?: string // 追加\n}\n```\n\n肝心のコンパイルですが，renderer を少しリファクタする必要があります．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  // ----------------------- ここから\n  const { props } = instance.vnode\n  initProps(instance, props)\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n  // ----------------------- ここまで\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\n`mountComponent` の上記に示した部分を `packages/runtime-core/component.ts` に切り出します．\n\n`packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n}\n```\n\n`packages/runtime-core/renderer.ts`\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // prettier-ignore\n  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n  setupComponent(instance)\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\nそれでは，setupComponent 内でコンパイルを実行していきましょう．\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n\n  // ------------------------ ここ\n  if (compile && !component.render) {\n    const template = component.template ?? ''\n    if (template) {\n      instance.render = compile(template)\n    }\n  }\n}\n```\n\nこれで template オプションで渡した簡素な HTML がコンパイルできるようになったはずなので playground で試してみましょう！\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n![Simple template compiler output before cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-before.png)\n\n無事に動いているようです．同じ構造であればコンパイルできるはずなので，少しいじってみて反映されるか確認してみましょう．\n\n```ts\nconst app = createApp({\n  template: `<b class=\"hello\" style=\"color: red;\">Hello World!!</b>`,\n})\napp.mount('#app')\n```\n\n![Simple template compiler output after cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-after.png)\n\nちゃんと実装できているようです！\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/070-more-complex-parser.md",
    "content": "# もっと複雑な HTML を書きたい\n\n## もっと複雑な HTML を書きたい\n\n今の状態だと，せいぜいタグの名前や属性，テキストの内容くらいしか表すことができていません．  \nそこで，もっと複雑な HTML を template に書けるようにしたいです．\n具体的には，これくらいのテンプレートをコンパイルできるようになりたいです．\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n\n  `,\n})\napp.mount('#app')\n```\n\nしかしこれだけ複雑なものは正規表現でパースするのは厳しいのです．\\\nなので，ここからは本格的にパーサを実装していこうと思います．\n\n## AST の導入\n\n本格的なコンパイラを実装していくにあたって AST というものを導入します．  \nAST は Abstract Syntax Tree (抽象構文木) の略で，名前の通り，構文を表現する木構造のデータ表現です．  \nこれは，Vue.js に限らず，さまざまなコンパイラを実装するときによく登場する概念です．  \n多くの場合(言語処理系においては)，「パース」というと，この AST という表現に変換することを指します．  \nAST の定義はそれぞれの言語が各自で定義します．  \n例えば，皆さんが馴染み深いであろう JavaScript は [estree](https://github.com/estree/estree) という AST で表現されていて，内部的にはソースコードの文字列がこの定義に沿ってパースされていたりします．\n\nと，少しかっこいい感じの説明をしてみましたが，イメージ的にはこれまで実装していた parse 関数の戻り値の型をもっとかっちり形式的に定義するだけです．\\\n今現状だと，parse 関数の戻り値は以下のようになっています．\n\n```ts\ntype ParseResult = {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}\n```\n\nこれを拡張して，もっと複雑な表現を行えるような定義にしてみます．\n\n新たに `~/packages/compiler-core/ast.ts` を作成します．  \n少し長いので，コード中に説明を書きながら説明を進めます．\n\n```ts\n// これは Node の種類を表すものです。\n// 注意するべき点としては、ここでいう Node というのは HTML の Node のことではなく、あくまでこのテンプレートコンパイラで扱う粒度であるということです。\n// なので、 Element やTextだけでなく Attribute も一つの Node として扱われます。\n// これは Vue.js の設計に沿った粒度で、今後、ディレクティブを実装する際などに役に立ちます。\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  ATTRIBUTE,\n}\n\n// 全ての Node は type と loc を持っています。\n// loc というのは location のことで、この Node がソースコード(テンプレート文字列)のどこに該当するかの情報を保持します。\n// (何行目のどこにあるかなど)\nexport interface Node {\n  type: NodeTypes\n  loc: SourceLocation\n}\n\n// Element の Node です。\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string // eg. \"div\"\n  props: Array<AttributeNode> // eg. { name: \"class\", value: { content: \"container\" } }\n  children: TemplateChildNode[]\n  isSelfClosing: boolean // eg. <img /> -> true\n}\n\n// ElementNode が持つ属性です。\n// ただの Record<string, string> と表現してしまってもいいのですが、\n// Vue に倣って name(string) と value(TextNode) を持つようにしています。\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE\n  name: string\n  value: TextNode | undefined\n}\n\nexport type TemplateChildNode = ElementNode | TextNode\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT\n  content: string\n}\n\n// location の情報です。 Node はこの情報を持ちます。\n// start, end に位置情報が入ります。\n// source には実際のコード(文字列)が入ります。\nexport interface SourceLocation {\n  start: Position\n  end: Position\n  source: string\n}\n\nexport interface Position {\n  offset: number // from start of file\n  line: number\n  column: number\n}\n```\n\nこれらが今回扱う AST です．  \nparse 関数では template の文字列をこの AST に変換するような実装をしていきます．\n\n## 本格的なパーサの実装\n\n::: warning\n2023 年 11 月下旬に vuejs/core で [パフォーマンス改善のための大規模なリライト](https://github.com/vuejs/core/pull/9674) が行われました．  \nこれらは 2023 年 の 12 月末に [Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) としてリリースされました．\\\nこのオンラインブックはそのリライト以前の実装を参考にしていることに注意しくてださい．  \n然るべきタイミングでこのオンラインブックも追従する予定です．  \n:::\n\n`~/packages/compiler-core/parse.ts` に本格的な実装していきます．  \n本格的と言ってもあまり身構えなくて大丈夫です．やっていることは基本的に文字列を読み進めながら分岐やループを活用して AST を生成しているだけです．  \nソースコードが少し多くなりますが，説明もコードベースの方が分かりやすいと思うのでそう進めていきます．  \n細かい部分はぜひソースコードを読んで把握してみてください．\n\n今実装してある baseParse の内容は一旦消して，戻り値の型も以下のようにします．\n\n```ts\nimport { TemplateChildNode } from './ast'\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  // TODO:\n  return { children: [] }\n}\n```\n\n## Context\n\nまずは parse する際に使う状態から実装します．これは `ParserContext` という名前をつけて，パース中に必要な情報をここにまとめます．\\\nゆくゆくはパーサーの設定オプションなども保持するようになると思います．\n\n```ts\nexport interface ParserContext {\n  // 元々のテンプレート文字列\n  readonly originalSource: string\n\n  source: string\n\n  // このパーサが読み取っている現在地\n  offset: number\n  line: number\n  column: number\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  }\n}\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content) // contextを生成\n\n  // TODO:\n  return { children: [] }\n}\n```\n\n## parseChildren\n\n順番的には，(parseChildren) -> (parseElement または parseText) とパースを進めていきます．\n\n少し長いですが，parseChildren の実装からです．説明はソースコード中のコメントアウトで行います．\n\n```ts\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content)\n  const children = parseChildren(context, []) // 子ノードをパースする\n  return { children: children }\n}\n\nfunction parseChildren(\n  context: ParserContext,\n\n  // HTMLは再起的な構造を持っているので、祖先要素をスタックとして持っておいて、子にネストして行くたびにpushしていきます。\n  // endタグを見つけるとparseChildrenが終了してancestorsをpopする感じです。\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (s[0] === '<') {\n      // sが\"<\"で始まり、かつ次の文字がアルファベットの場合は要素としてパースします。\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors) // TODO: これから実装します。\n      }\n    }\n\n    if (!node) {\n      // 上記の条件に当てはまらなかった場合はTextNodeとしてパースします。\n      node = parseText(context) // TODO: これから実装します。\n    }\n\n    pushNode(nodes, node)\n  }\n\n  return nodes\n}\n\n// 子要素パースの while を判定(パース終了)するための関数\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // sが\"</\"で始まり、かつその後にancestorsのタグ名が続くことを判定し、閉じタグがあるか(parseChildrenが終了するべきか)を判定します。\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString)\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  // nodeTypeがTextのものが連続している場合は結合してあげます\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes)\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content\n      return\n    }\n  }\n\n  nodes.push(node)\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1]\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, '</') &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || '>')\n  )\n}\n```\n\n続いて parseElement と parseText について実装していきます．\n\n::: tip isEnd のループについて\nisEnd では ancestors の配列のそれぞれの要素に対して startsWithEndTagOpen で s がその要素の閉じタグで始まっている文字列かどうかをループでチェックするような処理になっています．\n\n```ts\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // s が '</' で始まり、かつその後にancestorsのタグ名が続くことを判定し、閉じタグがあるか(parseChildrenが終了するべきか)を判定します。\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n```\n\nしかし，s が閉じタグで始まっている文字列かどうかをチェックするのであれば，ancestors の最後の要素に対してのみチェックすれば良いはずです．\\\nparser のリライトによってこのコードは無くなってしまいましたが，リライト前の Vue 3.3 のコードで ancestors の最後の要素に対してのみチェックするようにコードを書き換えても正常系のテストは全て PASS します．\n:::\n\n## parseText\n\nまずはシンプルな parseText の方から実装していきます．一部，parseText 以外でも使うユーティリティも実装しているので少しだけ長いです．\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  // \"<\" (タグの開始(開始タグ終了タグ問わず))まで読み進め、何文字読んだかを元にTextデータの終了時点のindexを算出します。\n  const endToken = '<'\n  let endIndex = context.source.length\n  const index = context.source.indexOf(endToken, 1)\n  if (index !== -1 && endIndex > index) {\n    endIndex = index\n  }\n\n  const start = getCursor(context) // これは loc 用\n\n  // endIndexの情報を元に Text データをパースします。\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n\n// content と length を元に text を抽出します。\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length)\n  advanceBy(context, length)\n  return rawText\n}\n\n// -------------------- 以下からはユーティリティです。(parseElementなどでも使う) --------------------\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context\n  advancePositionWithMutation(context, source, numberOfCharacters)\n  context.source = source.slice(numberOfCharacters)\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\n// 少し長いですが、やっていることは単純で、 pos の計算を行っています。\n// 引数でもらった pos のオブジェクトを破壊的に更新しています。\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0\n  let lastNewLinePos = -1\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++\n      lastNewLinePos = i\n    }\n  }\n\n  pos.offset += numberOfCharacters\n  pos.line += linesCount\n  pos.column =\n    lastNewLinePos === -1\n      ? pos.column + numberOfCharacters\n      : numberOfCharacters - lastNewLinePos\n\n  return pos\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context\n  return { column, line, offset }\n}\n\nfunction getSelection(\n  context: ParserContext,\n  start: Position,\n  end?: Position,\n): SourceLocation {\n  end = end || getCursor(context)\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  }\n}\n```\n\n## parseElement\n\n続いて要素のパースです．  \n要素のパースは主に start タグのパース，子 Node のパース，end タグのパースで成り立っていて，start タグのパースはさらにタグ名，属性に分かれます．  \nまずは前半の start タグ, 子 Node, end タグをパースするガワを作っていきましょう．\n\n```ts\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // Start tag.\n  const element = parseTag(context, TagType.Start) // TODO:\n\n  // <img /> のような self closing の要素の場合にはここで終了です。( children も end タグもないので)\n  if (element.isSelfClosing) {\n    return element\n  }\n\n  // Children.\n  ancestors.push(element)\n  const children = parseChildren(context, ancestors)\n  ancestors.pop()\n\n  element.children = children\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End) // TODO:\n  }\n\n  return element\n}\n```\n\nとくに難しいことはないと思います．ここで parseChildren が再帰しています．(parseElement は parseChildren に呼ばれるので)  \n前後で ancestors というスタック構造のデータを操作しています．\n\nparseTag を実装していきます．\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context)\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!\n  const tag = match[1]\n\n  advanceBy(context, match[0].length)\n  advanceSpaces(context)\n\n  // Attributes.\n  let props = parseAttributes(context, type)\n\n  // Tag close.\n  let isSelfClosing = false\n\n  // 属性まで読み進めた時点で、次が \"/>\" だった場合は SelfClosing とする\n  isSelfClosing = startsWith(context.source, '/>')\n  advanceBy(context, isSelfClosing ? 2 : 1)\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  }\n}\n\n// 属性全体(複数属性)のパース\n// eg. `id=\"app\" class=\"container\" style=\"color: red\"`\nfunction parseAttributes(\n  context: ParserContext,\n  type: TagType,\n): AttributeNode[] {\n  const props = []\n  const attributeNames = new Set<string>()\n\n  // タグが終わるまで読み続ける\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, '>') &&\n    !startsWith(context.source, '/>')\n  ) {\n    const attr = parseAttribute(context, attributeNames)\n\n    if (type === TagType.Start) {\n      props.push(attr)\n    }\n\n    advanceSpaces(context) // スペースは読み飛ばす\n  }\n\n  return props\n}\n\ntype AttributeValue =\n  | {\n      content: string\n      loc: SourceLocation\n    }\n  | undefined\n\n// 属性一つのパース\n// eg. `id=\"app\"`\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode {\n  // Name.\n  const start = getCursor(context)\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!\n  const name = match[0]\n\n  nameSet.add(name)\n\n  advanceBy(context, name.length)\n\n  // Value\n  let value: AttributeValue = undefined\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context)\n    advanceBy(context, 1)\n    advanceSpaces(context)\n    value = parseAttributeValue(context)\n  }\n\n  const loc = getSelection(context, start)\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  }\n}\n\n// 属性のvalueをパース\n// valueのクォートはシングルでもダブルでもパースできるように実装しています。\n// これも頑張ってクォートで囲まれたvalueを取り出したりしているだけです。\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context)\n  let content: string\n\n  const quote = context.source[0]\n  const isQuoted = quote === `\"` || quote === `'`\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1)\n\n    const endIndex = context.source.indexOf(quote)\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length)\n    } else {\n      content = parseTextData(context, endIndex)\n      advanceBy(context, 1)\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source)\n    if (!match) {\n      return undefined\n    }\n    content = parseTextData(context, match[0].length)\n  }\n\n  return { content, loc: getSelection(context, start) }\n}\n```\n\n## パーサの実装を終えて\n\n例になくたくさんコードを書いてきました．(せいぜい 300 行ちょっとですが)  \nここの実装は特別言葉で説明するよりも読んだ方が理解が進むと思うので，何度か繰り返し読んでみてください．  \nたくさん書きましたが基本的には文字列を読み進めて解析を進めているだけで，特に難しいテクニックなどはない地道な作業です．\n\nここまでで AST を生成できるようになっているはずです．パースができているか動作を確認してみましょう．\\\nとはいえ，codegen の部分をまだ実装できていないので，今回に関しては console に出力して確認してみます．\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\napp.mount('#app')\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim()) // templateはトリムしておく\n  console.log(\n    '🚀 ~ file: compile.ts:6 ~ baseCompile ~ parseResult:',\n    parseResult,\n  )\n\n  // TODO: codegen\n  // const code = generate(parseResult);\n  // return code;\n  return ''\n}\n```\n\n画面は何も表示されなくなってしまいますが，コンソールを確認してみましょう．\n\n![AST output for complex HTML](/figures/10-minimum-example/more-complex-parser/complex-html-ast.png)\n\nいい感じにパースができているようです．\\\nそれではここで生成した AST を元に codegen の方の実装を進めていこうと思います．\n\n\n## AST を元に render 関数を生成する\n\nさて，本格的なパーサが実装できたところで次はそれに適応したコードジェネレータを作っていきます．  \nと言っても今の時点だと複雑な実装は必要ありません．  \n先にコードをお見せしてしまいます．\n\n```ts\nimport { ElementNode, NodeTypes, TemplateChildNode, TextNode } from './ast'\n\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render() {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node)\n    case NodeTypes.TEXT:\n      return genText(node)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) => `${name}: \"${value?.content}\"`)\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``\n}\n```\n\n以上で動くようなものは作れます．\\\nパーサの章でコメントアウトした部分を戻して，実際に動作を見てみましょう．\\\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult)\n  return code\n}\n```\n\nplayground\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\n![Rendered template result in the browser](/figures/10-minimum-example/more-complex-parser/render-template-result.png)\n\nどうでしょうか．とってもいいっ感じに画面を描画できているようです．\n\nせっかくなので画面に動きをつけてみます．テンプレートへのバインディングは実装していないので，直接 DOM 操作します．\n\n```ts\nexport type ComponentOptions = {\n  // .\n  // .\n  // .\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | void // voidも許可する\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // マウント後に DOM 操作をしたいので Promise.resolve で処理を遅らせる\n    Promise.resolve().then(() => {\n      const btn = document.getElementById('btn')\n      btn &&\n        btn.addEventListener('click', () => {\n          const h2 = document.getElementById('hello')\n          h2 && (h2.textContent += '!')\n        })\n    })\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2 id=\"hello\">Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button id=\"btn\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\nこれで正常に動作していることを確認します．  \\\nどうでしょう．機能は少ないにしろ，だんだんと普段の Vue の開発者インタフェースに近づいてきたのではないでしょうか．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler2)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/080-template-binding.md",
    "content": "# データバインディング\n\n## テンプレートにバインドしたい\n\n今の状態だと，直接 DOM 操作をしているので，リアクティビティシステムや仮想 DOM の恩恵を得ることができていません．  \n実際にはイベントハンドラであったり，テキストの内容はテンプレート部分に書きたいわけです．それでこそ宣言的 UI の嬉しさと言った感じですよね．  \n以下のような開発者インタフェースを目指します．\n\n```ts\nimport { createApp, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>message: {{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\nsetup から return した値をテンプレートに記述して扱えるようにしたいのですが，このことをこれからは「テンプレートバインディング」であったり，単に「バインディング」という言葉で表現することにします．  \nバインディングをこれから実装していくわけですがイベントハンドラやマスタッシュ構文を実装する前にやっておきたいことがあります．  \n`setup から return した値` と言ったのですが，今 setup の戻り値は `undefined` または，`関数` (レンダー関数)です．  \nバインディングの実装の準備として，setup からステート等を return できるようにして，それらをコンポーネントのデータとして保持できるようにしておく必要があるようです．\n\n```ts\nexport type ComponentOptions = {\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void // Record<string, unknown> も返しうるように\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupState: Data // setup の結果はオブジェクトの場合はここに格納することにする\n}\n```\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n\n    // setupResultの型によって分岐をする\n    if (typeof setupResult === 'function') {\n      instance.render = setupResult\n    } else if (typeof setupResult === 'object' && setupResult !== null) {\n      instance.setupState = setupResult\n    } else {\n      // do nothing\n    }\n  }\n  // .\n  // .\n  // .\n}\n```\n\n伴って，これ以降，setup で定義されるデータのことを `setupState` と呼ぶことにします．\n\nさて，コンパイラを実装する前に，setupState をどのようにしてテンプレートにバインディングするか方針について考えてみます．  \nテンプレートを実装する前までは以下のように setupState をバインディングしていました．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return () => h('div', {}, [state.message])\n  },\n})\n```\n\nまぁ，バインドというより普通に render 関数がクロージャを形成し変数を参照しているだけです．  \nしかし今回は，イメージ的には setup オプションと render 関数は別のものなので，どうにかして render 関数に setup のデータを渡す必要があります．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  // これはrender関数に変換される\n  template: '<div>{{ state.message }}</div>',\n})\n```\n\ntemplate は h 関数を使った render 関数として compile され，instance.render に突っ込まれるわけなので，イメージ的には以下のようなコードと同等になります．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render() {\n    return h('div', {}, [state.message])\n  },\n})\n```\n\n当然，render 関数内では `state` という変数は定義されていません．  \nさて，どのようにして state を参照できるようにすれば良いでしょうか．\n\n## with 文\n\n結論から言うと，with 文を使って以下のようにすれば良いです．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render(ctx) {\n    with (ctx) {\n      return h('div', {}, [state.message])\n    }\n  },\n})\n```\n\nwith 文についてあまりよく知らない方も少なくないんじゃないかと思います．\n\nそれもそのはず，この機能は非推奨の機能です．\n\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with\n\nMDN によると，\n\n> まだ対応しているブラウザーがあるかもしれませんが、すでに関連するウェブ標準から削除されているか、削除の手続き中であるか、互換性のためだけに残されている可能性があります。使用を避け、できれば既存のコードは更新してください。\n\nとのことで，使用を避けるようにとのことです．\n\n今後の Vue.js の実装がどうなるかはわかりませんが，Vue.js 3 では with 文を使っているので，今回は with 文を使って実装していきます．\n\nここで少し補足なのですが，Vue.js でも，全てが全て with 文で実装されているわけではありません．  \nSFC で template を扱う際は with 文を使わずに実装されています．  \nこれについては後のチャプターで触れる予定ですが，とりあえずここでは with を使って実装することを考えてみます．\n\n---\n\nさて，ここで少し with 文の挙動についておさらいです．\nwith 文は，文に対するスコープチェーンを拡張します．\n\n以下のような挙動をとります．\n\n```ts\nconst obj = { a: 1, b: 2 }\n\nwith (obj) {\n  console.log(a, b) // 1, 2\n}\n```\n\nwith の引数として，state を持つ親オブジェクトを渡してあげれば，state を参照できるようになります．\n\n今回は，この親オブジェクトとして setupState を扱います．  \n実際には，setupState だけではなく，props のデータや OptionsApi で定義されたデータにもアクセスできるようになる必要があるのですが，今回は一旦 setupState のデータのみ使える形で良しとします．  \n(この辺りの実装は最小構成部門では取り上げず，後の部門で取り上げます．)\n\n今回やりたいことをまとめると，以下のようなテンプレートを\n\n```html\n<div>\n  <p>{{ state.message }}</p>\n  <button @click=\"changeMessage\">click me</button>\n</div>\n```\n\n以下のような関数にコンパイルして，\n\n```ts\n_ctx => {\n  with (_ctx) {\n    return h('div', {}, [\n      h('p', {}, [state.message]),\n      h('button', { onClick: changeMessage }, ['click me']),\n    ])\n  }\n}\n```\n\nこの関数に setupState を渡してあげることです．\n\n```ts\nconst setupState = setup()\nrender(setupState)\n```\n\n## マスタッシュ構文の実装\n\nまずはマスタッシュ構文の実装をしていきます．例によって，AST を考え，パーサの実装してコードジェネレータの実装をしていきます．\\\n今現時点で AST の Node として定義されているのは Element と Text と Attribute 程度です．\\\n新たにマスタッシュ構文を定義したいので，直感的には `Mustache` のような AST にすることが考えられます．\\\nそれにあたるのが `Interpolation` という Node です．\\\nInterpolation には「内挿」であったり，「挿入」と言った意味合いがあります．\\\nよって，今回扱う AST は次のようなものになります．\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION, // 追加\n\n  ATTRIBUTE,\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode // InterpolationNodeを追加\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // マスタッシュの中に記述された内容 (今回は setup で定義された単一の変数名がここに入る)\n}\n```\n\nAST が実装できたので，パースの実装をやっていきます．\n<span v-pre>`{{`</span> という文字列を見つけたら Interpolation としてパースします．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[]\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n\n    if (startsWith(s, \"{{\")) { // ここ\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n    // .\n    // .\n    //\n    }\n```\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  const [open, close] = ['{{', '}}']\n  const closeIndex = context.source.indexOf(close, open.length)\n  if (closeIndex === -1) return undefined\n\n  const start = getCursor(context)\n  advanceBy(context, open.length)\n\n  const innerStart = getCursor(context)\n  const innerEnd = getCursor(context)\n  const rawContentLength = closeIndex - open.length\n  const rawContent = context.source.slice(0, rawContentLength)\n  const preTrimContent = parseTextData(context, rawContentLength)\n\n  const content = preTrimContent.trim()\n\n  const startOffset = preTrimContent.indexOf(content)\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset)\n  }\n  const endOffset =\n    rawContentLength - (preTrimContent.length - content.length - startOffset)\n  advancePositionWithMutation(innerEnd, rawContent, endOffset)\n  advanceBy(context, close.length)\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\nText 中に <span v-pre>`{{`</span> が出現することもあるので parseText も少しだけいじります．\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = ['<', '{{'] // {{ が出現したらparseTextは終わり\n\n  let endIndex = context.source.length\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1)\n    if (index !== -1 && endIndex > index) {\n      endIndex = index\n    }\n  }\n\n  const start = getCursor(context)\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\nこれまでパーサを実装してきた方にとっては特に難しいことはないはずです． <span v-pre>`{{`</span> を探し， <span v-pre>`}}`</span> が来るまで読み進めて AST を生成しています．  \n<span v-pre>`}}`</span> が見つからなかった場合は undefined を返し，parseText への分岐でテキストとしてパースさせています．\n\nここらでちゃんとパースができているか，コンソール等に出力して確認してみましょう．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Interpolation AST output](/figures/10-minimum-example/template-binding/parse-interpolation-ast.png)\n\n問題なさそうです！\n\nさてそれではこの AST を元にバインディングを実装していきましょう．  \nrender 関数の中身を with 文で囲ってあげます．\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    // .\n    // .\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node)\n    // .\n    // .\n  }\n}\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`\n}\n```\n\nあとは，実際に render 関数を実行する際に引数として setupState を渡してあげましょう．\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild // 引数でctxを受け取れるように\n}\n```\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render, setupState } = instance\n    if (!instance.isMounted) {\n      // .\n      // .\n      // .\n      const subTree = (instance.subTree = normalizeVNode(render(setupState))) // setupStateを渡す\n      // .\n      // .\n      // .\n    } else {\n      // .\n      // .\n      // .\n      const nextTree = normalizeVNode(render(setupState)) // setupStateを渡す\n      // .\n      // .\n      // .\n    }\n  }\n}\n```\n\nここまで来ればレンダリングできるようになっているはずです．確認してみましょう！\n\n![Rendered interpolation result in the browser](/figures/10-minimum-example/template-binding/render-interpolation-result.png)\n\nこれにて初めてのバインディング，完です！\n\n## 初めてのディレクティブ\n\nさて，マスタッシュの次はインベントハンドラです．\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) =>\n      // props 名が @click だった場合にonClickに変換する\n      name === '@click'\n        ? `onClick: ${value?.content}`\n        : `${name}: \"${value?.content}\"`,\n    )\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n```\n\n動作を確認してみましょう．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n動きましたね！　やったね！　完成！\n\nと言いたいところですが，流石に実装が綺麗じゃないのでリファクタしていこうかと思います．\n`@click` というものはせっかく，「ディレクティブ」という名前で分類されていて，今後は v-bind や v-model を実装していくことは容易に想像できるかと思いますので，AST 上で `DIRECTIVE` と表現することにして，単純な `ATTRIBUTE` と区別するようにしておきましょう．\n\nいつも通り AST -> parse -> codegen の順で実装してみます．\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE, // 追加\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode> // props は Attribute と DirectiveNode のユニオンの配列にする\n  // .\n  // .\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  // v-name:arg=\"exp\" というような形式で表すことにする。\n  // eg. v-on:click=\"increment\"の場合は { name: \"on\", arg: \"click\", exp=\"increment\" }\n  name: string\n  arg: string\n  exp: string\n}\n```\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // --------------------------------------------------- ここから\n  // directive\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match =\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name\n      )!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n  // --------------------------------------------------- ここまで\n  // .\n  // .\n  // .\n```\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop))\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n```\n\nさて，playground で動作を確認してみましょう．\n`@click` のみならず，`v-on:click` や他のイベントもハンドリングできるようになっているはずです．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Compiled directive result in the browser](/figures/10-minimum-example/template-binding/compile-directives-result.png)\n\nやりました．かなり Vue に近づいてきました！  \nここまでで小さなテンプレートの実装は完了です．お疲れ様でした．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler3)\n\n<!-- ちゃんと動いているようなのでコンパイラ実装を始める際に分割した 3 つのタスクを実装し終えました。やったね！ -->\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/090-prerequisite-knowledge-for-the-sfc.md",
    "content": "# Single File Component で開発したい (周辺知識編)\n\n<KawaikoNote variant=\"question\" title=\"SFC って何？\">\n\nSFC は template, script, style を 1 ファイルにまとめた Vue 独自の形式です．\n`.vue` ファイルとして保存し，ビルドツールで JavaScript に変換されます！\n\n</KawaikoNote>\n\n## SFC はどうやって実現されている？\n\nここからはいよいよ SFC (Single File Component) の対応をやっていきます．  \nさて，どのように対応していきましょう．\\\nSFC はテンプレートと同様，開発時に使われるものでランタイム上には存在しません．  \nテンプレートの開発を終えたみなさんにとっては何をどのようにコンパイルすればいいかは簡単な話だと思います．\n\n以下のような SFC を\n\n```vue\n<script>\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>message: {{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n以下のような JS のコードに変換すれば良いのです．\n\n```ts\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render(_ctx) {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', `message: ${_ctx.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', [h('b', 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onClick: _ctx.changeMessage }, 'click me!'),\n    ])\n  },\n}\n```\n\nえっスタイルは！？ と思った方もいるかもしれませんが，一旦そのことは忘れて template と script について考えてみましょう．\\\nscript setup についても minimum example では触れません．\n\n## どのタイミングでいつどうやってコンパイルするの？\n\n結論から言ってしまうと，「ビルドツールが依存を解決するときにコンパイラを噛ませる」です．\n多くの場合 SFC は他のファイルから import して使います．\nこの時に，`.vue` というファイルが解決される際にコンパイルをして，結果を App にバインドさせるようなプラグインを書きます．\n\n```ts\nimport App from './App.vue' // App.vueが読み込まれるときにコンパイル\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\nさまざまなビルドツールがありますが，今回は Vite のプラグインを書いてみます．\n\nVite のプラグインを書いたことのない方も少ないと思うので，まずは簡単なサンプルコードでプラグインの実装に慣れてみましょう．\\\nとりあえず簡単な Vue のプロジェクトを作ってみます．\n\n```sh\npwd # ~\npnpm dlx create-vite\n## ✔ Project name: … plugin-sample\n## ✔ Select a framework: › Vue\n## ✔ Select a variant: › TypeScript\n\ncd plugin-sample\nni\n```\n\n作った PJ の vite.config.ts を見てみましょう．\n\n```ts\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue()],\n})\n```\n\n何やら @vitejs/plugin-vue を plugin に追加しているのがわかるかと思います．  \n実は，Vite で Vue の PJ を作るとき，SFC が使えているのはこれのおかげなのです．  \nこのプラグインには SFC のコンパイラが Vite のプラグインの API に沿って実装されていて，Vue ファイルを JS ファイルにコンパイルしています．  \nこのプロジェクトで簡単なプラグインを作ってみましょう．\n\n```ts\nimport { defineConfig, Plugin } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue(), myPlugin()],\n})\n\nfunction myPlugin(): Plugin {\n  return {\n    name: 'vite:my-plugin',\n\n    transform(code, id) {\n      if (id.endsWith('.sample.js')) {\n        let result = ''\n\n        for (let i = 0; i < 100; i++) {\n          result += `console.log(\"HelloWorld from plugin! (${i})\");\\n`\n        }\n\n        result += code\n\n        return { code: result }\n      }\n    },\n  }\n}\n```\n\nmyPlugin という名前で作ってみました．  \n簡単なので説明しなくても読める方も多いと思いますが一応説明しておきます．\n\nプラグインは Vite が要求する形式に合わせます．いろんなオプションがありますが，今回は簡単なサンプルなので transform オプションのみを使用しました．  \n他は公式ドキュメント等を眺めてもらえるのがいいかと思います．https://ja.vite.dev/guide/api-plugin\n\ntransform では `code` と `id` を受け取ることができます．code はファイルの内容，id はファイル名と思ってもらって良いです．\\\n戻り値として，code というプロパティに成果物を突っ込みます．  \nあとは id によってファイルの種類ごとに処理を書いたり，code をいじってファイルの内容を書き換えたりすれば OK です．  \n今回は，`*.sample.js`というファイルに対して，ファイルの内容の先頭に console を 100 個数仕込むように書き換えてみました．  \nでは実際に，適当な plugin.sample.js を実装をして確認してみます．\n\n```sh\npwd # ~/plugin-sample\ntouch src/plugin.sample.js\n```\n\n`~/plugin-sample/src/plugin.sample.js`\n\n```ts\nfunction fizzbuzz(n) {\n  for (let i = 1; i <= n; i++) {\n    i % 3 === 0 && i % 5 === 0\n      ? console.log('fizzbuzz')\n      : i % 3 === 0\n        ? console.log('fizz')\n        : i % 5 === 0\n          ? console.log('buzz')\n          : console.log(i)\n  }\n}\n\nfizzbuzz(Math.floor(Math.random() * 100) + 1)\n```\n\n`~/plugin-sample/src/main.ts`\n\n```ts\nimport { createApp } from 'vue'\nimport './style.css'\nimport App from './App.vue'\nimport './plugin.sample.js' // 追加\n\ncreateApp(App).mount('#app')\n```\n\nブラウザで確認してみましょう．\n\n```sh\npwd # ~/plugin-sample\npnpm dev\n```\n\n![Sample Vite plugin console output](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-console.png)\n\n![Sample Vite plugin transformed source](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-source.png)\n\nちゃんとソースコードが改変されていることがわかります．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/091-parse-sfc.md",
    "content": "## SFC パーサを実装していく\n\n## 準備\n\n先ほど作ったサンプルのプラグインなのですが，もう不要なので消してしまいましょう．\n\n```sh\npwd # ~\nrm -rf ./plugin-sample\n```\n\nまた Vite の plugin を作成するため Vite 本体をインストールしておきます．\n\n```sh\npwd # ~\npnpm add vite\n```\n\nplugin の本体なのですが，本来これは vuejs/core の範囲外なので packages に　`@extensions`　というディレクトリを切ってそこに実装していきます．\n\n```sh\npwd # ~\nmkdir -p packages/@extensions/vite-plugin-chibivue\ntouch packages/@extensions/vite-plugin-chibivue/index.ts\n```\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      return { code }\n    },\n  }\n}\n```\n\nここから SFC のコンパイラを実装していくのですが，実態がないとイメージが湧きづらいかと思うので playground を実装してみて，動かしながらやっていこうかと思います．  \n簡単な SFC とその読み込みを行います．\n\n```sh\npwd # ~\ntouch examples/playground/src/App.vue\n```\n\n`examples/playground/src/App.vue`\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n`playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n`playground/vite.config.js`\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nimport chibivue from '../../packages/@extensions/vite-plugin-chibivue'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n  plugins: [chibivue()],\n})\n```\n\nこの状態で起動してみましょう．\n\n![Vite error before the SFC plugin is implemented](/figures/10-minimum-example/parse-sfc/vite-error.png)\n\nもちろんエラーになります．やったね( ？ )\n\n## エラーの解消\n\nとりあえずエラーを解消していきましょう．いきなり完璧なものは目指しません．  \nまず，transform の対象を「\\*.vue」に限定してあげましょう．\\\nsample でやったように id で分岐を書いてもいいのですが，せっかく vite から createFilter という関数が提供されているのでそれでフィルターを作ります．(特に理由はないです．)\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\nフィルターを作り，vue ファイルだった場合はファイル内容 `export default {}` に transform してみました．  \nおそらくエラーは消え，画面は何も表示されない感じになっているかと思います．\n\n## パーサの実装 on compiler-sfc\n\nさて，これではただのその場しのぎなのでちゃんとした実装をしていきます．  \nvite-plugin での役割はあくまで vite を利用する際に vite で transform できるようにするためのものなので，パースやコンパイラは vue の本体にあります．  \nそれが`compiler-sfc`というディレクトリです．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\nSFC のコンパイラは vite だろうが webpack だろうがコアな部分は同じです．それらの実装をになっているのが `compiler-sfc` です．\n\n`compiler-sfc` を作っていきましょう．\n\n```sh\npwd # ~\nmkdir packages/compiler-sfc\ntouch packages/compiler-sfc/index.ts\n```\n\nSFC のコンパイルでは `SFCDescriptor` というオブジェクトで SFC を表現します．\n\n```sh\ntouch packages/compiler-sfc/parse.ts\n```\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { SourceLocation } from '../compiler-core'\n\nexport interface SFCDescriptor {\n  id: string\n  filename: string\n  source: string\n  template: SFCTemplateBlock | null\n  script: SFCScriptBlock | null\n  styles: SFCStyleBlock[]\n}\n\nexport interface SFCBlock {\n  type: string\n  content: string\n  loc: SourceLocation\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: 'template'\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: 'script'\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: 'style'\n}\n```\n\nまあ，特に難しいことはないです．SFC の情報をオブジェクトで表現しただけです．\n\n`packages/compiler-sfc/parse.ts` では SFC ファイル(文字列)を `SFCDescriptor` にパースします．  \n「ええ．あんだけテンプレートのパースを頑張ったのにまたパーサつくるのかよ．．面倒臭い」と思った方もいるかも知れませんが，安心してください．  \nここで実装するパーサは大したものではないです．というのも，これまで作ってきたものを組み合わせて template，script，style を分離するだけなので楽ちんです．\n\nまず，下準備として以前作った template のパーサを export してあげます．\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nimport { baseCompile, baseParse } from '../compiler-core'\n\nexport function compile(template: string) {\n  return baseCompile(template)\n}\n\n// パーサをexportしてあげる\nexport function parse(template: string) {\n  return baseParse(template)\n}\n```\n\nこれらの interface を compiler-sfc 側で持っておいてあげます．\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/compileTemplate.ts\n```\n\n`~/packages/compiler-sfc/compileTemplate.ts`\n\n```ts\nimport { TemplateChildNode } from '../compiler-core'\n\nexport interface TemplateCompiler {\n  compile(template: string): string\n  parse(template: string): { children: TemplateChildNode[] }\n}\n```\n\nあとはパーサを実装してあげるだけです．\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { ElementNode, NodeTypes, SourceLocation } from '../compiler-core'\nimport * as CompilerDOM from '../compiler-dom'\nimport { TemplateCompiler } from './compileTemplate'\n\n/**\n * =========\n * 一部省略\n * =========\n */\n\nexport interface SFCParseOptions {\n  filename?: string\n  sourceRoot?: string\n  compiler?: TemplateCompiler\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor\n}\n\nexport const DEFAULT_FILENAME = 'anonymous.vue'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  }\n\n  const ast = compiler.parse(source)\n  ast.children.forEach(node => {\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    switch (node.tag) {\n      case 'template': {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock\n        break\n      }\n      case 'script': {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock\n        descriptor.script = scriptBlock\n        break\n      }\n      case 'style': {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock)\n        break\n      }\n      default: {\n        break\n      }\n    }\n  })\n\n  return { descriptor }\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag\n\n  let { start, end } = node.loc\n  start = node.children[0].loc.start\n  end = node.children[node.children.length - 1].loc.end\n  const content = source.slice(start.offset, end.offset)\n\n  const loc = { source: content, start, end }\n  const block: SFCBlock = { type, content, loc }\n\n  return block\n}\n```\n\nここまでパーサを実装してきたみなさんにとっては簡単だと思います．  \n実際に SFC を plugin 側でパースしてみましょう．\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport { parse } from '../../compiler-sfc'\n\nexport default function vitePluginChibivue(): Plugin {\n  //.\n  //.\n  //.\n  return {\n    //.\n    //.\n    //.\n    transform(code, id) {\n      if (!filter(id)) return\n      const { descriptor } = parse(code, { filename: id })\n      console.log(\n        '🚀 ~ file: index.ts:14 ~ transform ~ descriptor:',\n        descriptor,\n      )\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\nこのコードは vite が動いているプロセス，つまり node で実行されるので console はターミナルに出力されているかと思います．\\\n\n![SFC descriptor before parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-before.png)\n\n/_ 途中省略 _/\n\n![SFC descriptor after parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-after.png)\n\n無事にパースできているようです．やったね！\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler2)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/092-compile-sfc-template.md",
    "content": "# SFC の template block のコンパイル\n\n## コンパイラの切り替え\n\nパース結果の `descriptor.script.content` と `descriptor.template.content` にはそれぞれのソースコードが入っています．  \nこれらを使って上手くコンパイルしたいです．template の方からやっていきましょう．  \nテンプレートのコンパイラはすでに持っています．  \nしかし，以下のコードを見てもらえればわかるのですが，\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n```\n\nこれは Function コンストラクタで new する前提の物になってしまっているので先頭に return がついてしまっています．\\\nSFC のコンパイラでは render 関数だけを生成したいので，コンパイラのオプションで分岐できるようにしましょう．\\\nコンパイラの第 2 引数としてオプションを受け取れるようにし，`isBrowser` というフラグを指定可能にします．\\\nこの変数が true の時はランタイム上で new される前提のコードを出力し，false の場合は単にコードを生成します．\n\n```sh\npwd # ~\ntouch packages/compiler-core/options.ts\n```\n\n`packages/compiler-core/options.ts`\n\n```ts\nexport type CompilerOptions = {\n  isBrowser?: boolean\n}\n```\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(template, defaultOption)\n}\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult, option)\n  return code\n}\n```\n\n`~/packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n```\n\nついでに import 文を足しておきました．output という配列にソースコードを詰めていく感じにも変更してます．\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\\n\")\n\n      const { descriptor } = parse(code, { filename: id })\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { render }`)\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\n## 問題点\n\nこれで render 関数をコンパイルできるようになっていると思います．ブラウザの source で確認してみましょう．\\\n\nと，言いたいところなのですが，実は少し問題があります．\n\nデータをテンプレートにバインドする際に，with 文を使用していると思うのですが，Vite は ESM を扱う都合上，非厳格モード (sloppy モード) でのみ動作するコードを処理できず，with 文を扱うことができません．\\\nこれまでは vite 上ではなく，単に with 文を含むコード(文字列)を Function コンストラクタに渡してブラウザ上で関数化していたので特に問題にはなっていませんでしたが，今回はエラーになってしいます．\\\n以下のようなエラーが出るはずです．\n\n> Strict mode code may not include a with statement\n\nこれについては Vite の公式ドキュメントの方にもトラブルシューティングとして記載されています．\n\n[Syntax Error / Type Error が発生する (Vite)](https://ja.vite.dev/guide/troubleshooting.html)\n\n今回は，一時的な対応策として，ブラウザモードでない場合には with 文を含まないコードを生成するようにしてみます．\n\n具体的には，バインド対象のデータに関しては with 文を使用せずに prefix として `_ctx.` を付与する形で制御してみます．\\\n一時的な対応なのであまり厳格ではないのですが，概ね動作するようになると思います．  \n(ちゃんとした対応は後のチャプターで行います．)\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  // isBrowser が false の場合は with 文を含まないコードを生成する\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n    ${option.isBrowser ? 'with (_ctx) {' : ''}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? '}' : ''}\n}`\n}\n\n// .\n// .\n// .\n\nconst genNode = (\n  node: TemplateChildNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option)\n    case NodeTypes.TEXT:\n      return genText(node)\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (\n  el: ElementNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop, option))\n    .join(', ')}}, [${el.children.map(it => genNode(it, option)).join(', ')}])`\n}\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${\n            option.isBrowser ? '' : '_ctx.' // -------------------- ここ\n          }${prop.exp}`\n        default:\n          // TODO: other directives\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n\n// .\n// .\n// .\n\nconst genInterpolation = (\n  node: InterpolationNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? '' : '_ctx.'}${node.content}` // ------------ ここ\n}\n```\n\n![Compiled SFC template render result](/figures/10-minimum-example/compile-sfc-template/compiled-render-result.png)\n\n上手くコンパイルできているようです．あとは同じ要領で，どうにかして script を引っこ抜いて default exports に突っ込めば OK です．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler3)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/093-compile-sfc-script.md",
    "content": "# SFC の script block のコンパイル\n\n## やりたいこと\n\nさて，元々の SFC の script 部分は以下のようになっています．\n\n```ts\nexport default {\n  setup() {},\n}\n```\n\nこれらを先ほど生成した render 関数といい感じに mix して export したいのですが，どうにか\n\n```ts\n{\n  setup() {},\n}\n```\n\nの部分だけ取り出せないでしょうか？\n\nもしこの部分を取り出すことができたら，\n以下のようにしてあげれば良いことになります．\n\n```ts\nconst _sfc_main = {\n  setup() {},\n}\n\nexport default { ..._sfc_main, render }\n```\n\n## 外部ライブラリを使う\n\n上記のようなことをしたいのですが結論から言うと以下の 2 つのライブラリを使って楽に実装します．\n\n- @babel/parser\n- magic-string\n\n### Babel\n\nhttps://babeljs.io\n\n[What is Babel](https://babeljs.io/docs)\n\nこちらは普段 JavaScript を使っている方はよく聞くかも知れません．  \nBabel は JavaScript の後方互換バージョンに変換するために使用されるツールチェインです．  \n簡単に言うと，JS から JS へのコンパイラ(トランスパイラ)です．  \n\n今回は Babel をコンパイラとしてだけではなく，パーサとして利用します．  \nBabel はコンパイラとしての役割を持つので，もちろん内部では AST に変換するためのパーサを実装しています．  \nそのパーサをライブラリとして利用ます．  \n\nさらっと AST という言葉を出しましたが，JavaScript ももちろん AST としての表現を持っています．  \nこちらに AST の仕様があります．(https://github.com/estree/estree)  \n上記の GitHub の md ファイルを見てもらっても良いのですが，簡単に JavaScript の AST について説明しておくと，  \nまずプログラム全体は Program という AST ノードで表現されていて，Statement を配列で持ちます．(わかりやすいように TS の interface で表現しています．)\n\n```ts\ninterface Program {\n  body: Statement[]\n}\n```\n\nStatement というのは日本で言うと「文」です．JavaScript は文の集まりです．\\\n具体的には「変数宣言文」や「if 文」「for 文」「ブロック」などが挙げられます．\n\n```ts\ninterface Statement {}\n\ninterface VariableDeclaration extends Statement {\n  /* 省略 */\n}\n\ninterface IfStatement extends Statement {\n  /* 省略 */\n}\n\ninterface ForStatement extends Statement {\n  /* 省略 */\n}\n\ninterface BlockStatement extends Statement {\n  body: Statement[]\n}\n// 他にもたくさんある\n```\n\nそして，文というのは多くの場合「Expression(式)」を持ちます．\\\n式というのは変数に代入できる物だと考えてもらえれば良いです．\\\n具体的には「オブジェクト」や「2 項演算」「関数呼び出し」などが挙げられます．\n\n```ts\ninterface Expression {}\n\ninterface BinaryExpression extends Expression {\n  operator: '+' | '-' | '*' | '/' // 他にもたくさんあるが省略\n  left: Expression\n  right: Expression\n}\n\ninterface ObjectExpression extends Expression {\n  properties: Property[] // 省略\n}\n\ninterface CallExpression extends Expression {\n  callee: Expression\n  arguments: Expression[]\n}\n\n// 他にもたくさんある\n```\n\nif 文について考えると，このような構造をとることがわかります．\n\n```ts\ninterface IfStatement extends Statement {\n  test: Expression // 条件値\n  consequent: Statement // 条件値がtrueの場合に実行される文\n  alternate: Statement | null // 条件値がfalseの場合に実行される文\n}\n```\n\nこのように，JavaScript の構文は上記のような AST にパースされるのです．\\\n既に chibivue のテンプレートのコンパイラを実装したみなさんにとっては分かりやすい話だと思います．(同じこと)\n\nなぜ Babel を使うのかというと，理由は２つあって，1 つは単純にめんどくさいからです．\\\nパーサを実装したことあるみなさんなら estree を見ながら JS のパーサを実装することも技術的には可能かも知れません．\\\nけれども，とてもめんどくさいし，今回の「Vue の理解を深める」という点においてはあまり重要ではありません．\\\nもう一つの理由は本家 Vue もこの部分は Babel を使っているという点です．\n\n### magic-string\n\nhttps://github.com/rich-harris/magic-string\n\nもう一つ使いたいライブラリがあります．こちらも本家の Vue が使っているライブラリです．  \nこちらは文字列操作を便利にするライブラリです．\n\n```ts\nconst input = 'Hello'\nconst s = new MagicString(input)\n```\n\nのようにインスタンスを生成し，そのインスタンスに生えている便利なメソッドを利用して文字列操作をしていきます．\\\nいくつか例をあげます．\n\n```ts\ns.append('!!!') // 末尾に追加する\ns.prepend('message: ') // 先頭に追加する\ns.overwrite(9, 13, 'こんにちは') // 範囲を指定して上書き\n```\n\n特に無理して使う必要はないのですが，本家の Vue に合わせて使うことにします．\n\nBabel にしろ magic-string にしろ，実際の使い方等は実装の段階で合わせて説明するのでなんとなくの理解で問題ないです．\n\n## script の default export を書き換える\n\n今一度現在の目標を確認しておくと，\n\n```ts\nexport default {\n  setup() {},\n  // その他のオプション\n}\n```\n\nというコードを，\n\n```ts\nconst _sfc_main = {\n  setup() {},\n  // その他のオプション\n}\n\nexport default { ..._sfc_main, render }\n```\n\nというふうに書き換えたいわけです．\n\nつまりは，元々のコードの export 文から良い感じに export 対象を抜き出し，\\_sfc_main という変数に代入できるようになればゴールということです．\n\nまずは必要なライブラリをインストールします．\n\n```sh\npwd # ~\npnpm add @babel/parser magic-string\n```\n\nrewriteDefault.ts というファイルを作成します．\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/rewriteDefault.ts\n```\n\ninput に対象のソースコード，as に最終的にバインドしたい変数名を受け取れるようにしておきます．  \n戻り値として変換されたソースコードを返します．\n\n`~/packages/compiler-sfc/rewriteDefault.ts`\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // TODO:\n  return ''\n}\n```\n\nまず手始めとして，そもそも export の宣言が存在しない場合のハンドリングをしておきます．\nexport が存在しないわけなので，からのオブジェクトをバインドして終了です．\n\n```ts\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`\n  }\n\n  // TODO:\n  return ''\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input)\n}\n```\n\nここで Babel パーサと magic-string の登場です．\n\n```ts\nimport { parse } from '@babel/parser'\nimport MagicString from 'magic-string'\n// .\n// .\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  const s = new MagicString(input)\n  const ast = parse(input, {\n    sourceType: 'module',\n  }).program.body\n  // .\n  // .\n}\n```\n\nここからは Babel パーサによって得られた JavaScript の AST(ast) を元に s を文字列操作していきます．\\\n少し長いですが，ソースコード内のコメントで補足の説明も入れていきます．\\\n基本的には AST を手繰っていって，type によって分岐処理を書いて magic-string のメソッドで s を操作していくだけです．\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  ast.forEach(node => {\n    // default exportの場合\n    if (node.type === 'ExportDefaultDeclaration') {\n      if (node.declaration.type === 'ClassDeclaration') {\n        // `export default class Hoge {}` だった場合は、`class Hoge {}` に置き換える\n        s.overwrite(node.start!, node.declaration.id.start!, `class `)\n        // その上で、`const ${as} = Hoge;` というようなコードを末尾に追加してあげればOK.\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`)\n      } else {\n        // それ以外の default exportは宣言部分を変数宣言に置き換えてあげればOk.\n        // eg 1) `export default { setup() {}, }`  ->  `const ${as} = { setup() {}, }`\n        // eg 2) `export default Hoge`  ->  `const ${as} = Hoge`\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)\n      }\n    }\n\n    // named export の場合でも宣言中に default exportが発生する場合がある.\n    // 主に3パターン\n    //   1. `export { default } from \"source\";`のような宣言の場合\n    //   2. `export { hoge as default }` from 'source' のような宣言の場合\n    //   3. `export { hoge as default }` のような宣言の場合\n    if (node.type === 'ExportNamedDeclaration') {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === 'ExportSpecifier' &&\n          specifier.exported.type === 'Identifier' &&\n          specifier.exported.name === 'default'\n        ) {\n          // `from`というキーワードがある場合\n          if (node.source) {\n            if (specifier.local.name === 'default') {\n              // 1. `export { default } from \"source\";`のような宣言の場合\n              // この場合はimport文に抜き出して名前をつけてあげ、最終的な変数にバインドする\n              // eg) `export { default } from \"source\";`  ->  `import { default as __VUE_DEFAULT__ } from 'source'; const ${as} = __VUE_DEFAULT__`\n              const end = specifierEnd(input, specifier.local.end!, node.end!)\n              s.prepend(\n                `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`,\n              )\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`)\n              continue\n            } else {\n              // 2. `export { hoge as default }` from 'source' のような宣言の場合\n              // この場合は一度全てのspecifierをそのままimport文に書き換え、as defaultになっている変数を最終的な変数にバインドする\n              // eg) `export { hoge as default } from \"source\";`  ->  `import { hoge } from 'source'; const ${as} = hoge\n              const end = specifierEnd(\n                input,\n                specifier.exported.end!,\n                node.end!,\n              )\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              )\n\n              // 3. `export { hoge as default }`のような宣言の場合\n              // この場合は単純に最終的な変数にバインドしてあげる\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = ${specifier.local.name}`)\n              continue\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!)\n          s.overwrite(specifier.start!, end, ``)\n          s.append(`\\nconst ${as} = ${specifier.local.name}`)\n        }\n      }\n    }\n  })\n  return s.toString()\n}\n\n// 宣言文の終端を算出する\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false\n  let oldEnd = end\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++\n    } else if (input.charAt(end) === ',') {\n      end++\n      hasCommas = true\n      break\n    } else if (input.charAt(end) === '}') {\n      break\n    }\n  }\n  return hasCommas ? end : oldEnd\n}\n```\n\nこれで default export の書き換えができるようになりました．\\\n実際に plugin で使ってみましょう．\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse, rewriteDefault } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n\n      const { descriptor } = parse(code, { filename: id })\n\n      // --------------------------- ここから\n      const SFC_MAIN = '_sfc_main'\n      const scriptCode = rewriteDefault(\n        descriptor.script?.content ?? '',\n        SFC_MAIN,\n      )\n      outputs.push(scriptCode)\n      // --------------------------- ここまで\n\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { ...${SFC_MAIN}, render }`) // ここ\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\nその前にちょっとだけ修正します．\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n  // componentのrenderオプションをインスタンスに\n  const { render } = component\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n}\n```\n\nこれでレンダリングができるようになっているはずです！！！\n\n![Rendered SFC script result](/figures/10-minimum-example/compile-sfc-script/render-sfc-result.png)\n\nスタイルの対応をしていないのでスタイルが当たっていないですがこれでレンダリングはできるようになりました．\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/094-compile-sfc-style.md",
    "content": "# SFC の style block の実装\n\n## 仮想モジュール\n\nスタイルも対応してしまいます．vite では css という拡張子のファイルを import することでスタイルを読み込めるようになっています．\n\n```js\nimport 'app.css'\n```\n\nVite の仮想モジュールという機能を使って SFC から仮想的な CSS ファイルを作り，アウトプットの JS ファイルの import 文に追加する方針で実装してみます．  \n仮想モジュール，と聞くとなんだか難しいように聞こえますが，「実際には存在しないファイルをあたかも存在するようにインメモリに保持しておける」と捉えてもらえれば問題ないです．  \nVite では `load` と `resolveId` というオプションを使って仮想モジュールを実現することができます．\n\n```ts\nexport default function myPlugin() {\n  const virtualModuleId = 'virtual:my-module'\n\n  return {\n    name: 'my-plugin', // 必須、警告やエラーで表示されます\n    resolveId(id) {\n      if (id === virtualModuleId) {\n        return virtualModuleId\n      }\n    },\n    load(id) {\n      if (id === virtualModuleId) {\n        return `export const msg = \"from virtual module\"`\n      }\n    },\n  }\n}\n```\n\nresolveId に解決したいモジュールの id を任意に設定し，load でその id をハンドリングすることによってモジュールを読み込むことができます．  \n上記の例だと，`virtual:my-module` というファイルは実際には存在しませんが，\n\n```ts\nimport { msg } from 'virtual:my-module'\n```\n\nのように書くと `export const msg = \"from virtual module\"` が load されます．\n\n[参考](https://ja.vite.dev/guide/api-plugin)\n\nこの仕組みを使って SFC の style ブロックを仮想の css ファイルとして読み込むようにしてみます．  \n最初に言った通り，vite では css という拡張子のファイルを import すれば良いので，${SFC のファイル名}.css という仮想モジュールを作ることを考えてみます．\n\n## SFC のスタイルブロックの内容で仮想モジュールを実装する\n\n今回は，たとえば「App.vue」というファイルがあったとき，その style 部分を「App.vue.css」という名前の仮想モジュールを実装することを考えてみます．  \nやることは単純で，`**.vue.css` という名前のファイルが読み込まれたら `.css` を除いたファイルパス(つまり通常の Vue ファイル)から SFC を `fs.readFileSync` で取得し，  \nパースして style タグの内容を取得し，それを code として返します．\n\n```ts\nexport default function vitePluginChibivue(): Plugin {\n  //  ,\n  //  ,\n  //  ,\n  return {\n    //  ,\n    //  ,\n    //  ,\n    resolveId(id) {\n      // このidは実際には存在しないパスだが、loadで仮想的にハンドリングするのでidを返してあげる (読み込み可能だということにする)\n      if (id.match(/\\.vue\\.css$/)) return id\n\n      // ここでreturnされないidに関しては、実際にそのファイルが存在していたらそのファイルが解決されるし、存在していなければ存在しないというエラーになる\n    },\n    load(id) {\n      // .vue.cssがloadされた (importが宣言され、読み込まれた) ときのハンドリング\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, '')\n        const content = fs.readFileSync(filename, 'utf-8') // 普通にSFCファイルを取得\n        const { descriptor } = parse(content, { filename }) //  SFCをパース\n\n        // contentをjoinsして結果とする。\n        const styles = descriptor.styles.map(it => it.content).join('\\n')\n        return { code: styles }\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n      outputs.push(`import '${id}.css'`) // ${id}.cssのimport文を宣言しておく\n      //  ,\n      //  ,\n      //  ,\n    },\n  }\n}\n```\n\nさて，ブラウザで確認してみましょう．\n\n![Virtual CSS module request in the browser](/figures/10-minimum-example/compile-sfc-style/load-virtual-css-module.png)\n\nちゃんとスタイルが当たるようになっているようです．\n\nブラウザの方でも，css が import され，.vue.css というファイルが仮想的に生成されているのが分かるかと思います．  \n![Loaded CSS module in the browser](/figures/10-minimum-example/compile-sfc-style/loaded-css-in-browser.png)\n![Generated Vue CSS module](/figures/10-minimum-example/compile-sfc-style/generated-vue-css-module.png)\n\nこれで SFC が使えるようになりました！\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler4)\n"
  },
  {
    "path": "book/online-book/src/ja/10-minimum-example/100-break.md",
    "content": "# ちょっと一息\n\n<KawaikoNote variant=\"surprise\" title=\"お疲れ様でした！\">\n\nMinimal Example 部門，完走おめでとうございます！\nここまでで Vue.js の核心部分を一通り体験しました．\n\n</KawaikoNote>\n\n## Minimal Example 部門はここまで！\n\n冒頭で，この本はいくつかの部門に分かれるという話をしたのですが，それの一番最初の部門である「Minimal Example 部門」はここまでで終了です．お疲れ様でした！\\\n仮想 DOM やパッチレンダリング周りに興味がある人は Basic Virtual DOM 部門に進めばいいですし，コンポーネントをもっと拡張したければ Basic Component 部門，\\\nテンプレートでもっと豊かな表現(ディレクティブなど)に興味があれば Basic Template Compiler 部門，script setup やコンパイラマクロに興味があれば Basic SFC Compiler 部門に進めば良いです．(勿論全部やってもいいですよ！！)\\\nそして何よりこのこの「Minimal Example 部門」もひとつの立派な部門なわけですから，「そんなに深くは知らなくてもいいけど，全体的にサラッとやりたい！」という方はここまでで十分なのです．\\\n\n## ここまでで何ができるようになった？\n\n最後に，少し Minimal Example 部門でやったこととできるようになったことを振り返ってみましょう．\n\n## いつもみているものが何処の何なのか，分かるようになった\n\nまず，createApp という最初の開発者インタフェースを通して，(Web アプリの)開発者と Vue の世界がどのようなふうに繋がっているのかを理解しました．  \n具体的には，最初にやったリファクタを起点に，Vue のディレクトリ構造の基盤とそれぞれの依存関係，そして開発者が触っている部分はどこのなんなのかというのが分かるようになっているはずです．\\\nここらで今現状でのディレクトリと，vuejs/core のディレクトリを見比べてみましょう．\n\nchibivue\n![Minimum example implementation artifacts](/figures/10-minimum-example/break/minimum-example-artifacts.png)\n\n※ 本家のコードはデカくてスクショに収まりきらないので割愛\n\nhttps://github.com/vuejs/core\n\n小さいなりに，それぞれのファイルの役割やその中身もそこそこ読めるようになっているのではないでしょうか．\nぜひ，今回触れていない部分のソースコードのコードリーディングにも挑戦してみてほしいです．(ぼちぼち読めるはずです！　)\n\n## 宣言的 UI の実現方法が分かった\n\nh 関数の実装を通して，宣言的 UI はどうやって実現されているかということについて理解しました．\n\n```ts\n// 内部的に {tag, props, children} のようなオブジェクトを生成し、それを元にDOM操作をしている\nh('div', { id: 'my-app' }, [\n  h('p', {}, ['Hello!']),\n  h(\n    'button',\n    {\n      onClick: () => {\n        alert('hello')\n      },\n    },\n    ['Click me!'],\n  ),\n])\n```\n\nここで初めて Virtual DOM のようなものが登場しました．\n\n## リアクティビティシステムとは何か，どうやって画面を動的に更新していくかということが分かった\n\nVue の醍醐味である，リアクティビティシステムがどのような実装で成り立っているのか，そもそもリアクティビティシステムとはなんのことなのか，ということについて理解しました\n\n```ts\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nfunction reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, {\n    get(target: object, key: string | symbol, receiver: object) {\n      track(target, key)\n      return Reflect.get(target, key, receiver)\n    },\n\n    set(\n      target: object,\n      key: string | symbol,\n      value: unknown,\n      receiver: object,\n    ) {\n      Reflect.set(target, key, value, receiver)\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n```ts\nconst component = {\n  setup() {\n    const state = reactive({ count: 0 }) // create proxy\n\n    const increment = () => {\n      state.count++ // trigger\n    }\n\n    ;() => {\n      return h('p', {}, `${state.count}`) // track\n    }\n  },\n}\n```\n\n## 仮想 DOM とはなんなのか，何が嬉しいのか，どうやって実装するのかが分かった\n\nh 関数を使ったレンダリングの改善として，仮想 DOM の比較による差分レンダリングの方法について理解しました．\n\n```ts\n// 仮想DOMのinterface\nexport interface VNode<HostNode = any> {\n  type: string | typeof Text | object\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined\n}\n\n// まず、render関数が呼ばれる\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  // 初回は n1 が null. この場合は各自 process で mount が走る\n  patch(null, vnode, container)\n}\n\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\n// 2回目以降はひとつ前のVNodeと現在のVNodeをpatch関数に渡すことで差分を更新する\nconst nextVNode = component.render()\npatch(prevVNode, nextVNode)\n```\n\n## コンポーネントの構造とコンポーネント間でのやりとりをどう実現するのかが分かった．\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component\n\n  vnode: VNode\n  subTree: VNode\n  next: VNode | null\n  effect: ReactiveEffect\n  render: InternalRenderFunction\n  update: () => void\n\n  propsOptions: Props\n  props: Data\n  emit: (event: string, ...args: any[]) => void\n\n  isMounted: boolean\n}\n```\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n## コンパイラとは何か，テンプレートの機能はどう実現されているのかが分かった\n\nコンパイラとはどのようなものかについて理解し，テンプレートのコンパイラを実装することで，より生の HTML に近く，かつマスタッシュ構文などの Vue 固有な機能についての実装を理解しました．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n## Vite プラグインを通して SFC コンパイラの実現方法について理解した．\n\n実装して template コンパイラを活用して，script, template, style を一つのファイルに記述するオリジナルのファイルフォーマットをどう実現するかについて理解しました．  \nvite プラグインでどういうことができるのか，transform や仮想モジュールについて学びました．\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n## これからについて\n\nこれからは，より実用的なものにしていくにあたって，それぞれのパートをより詳しくやっていきます．\\\nそして，これから各部門でやることと，進め方(方針)について少し説明します．\n\n## どういうことをやるのか\n\nこの本の冒頭と重複する部分も多いですが，改めて．  \nここからは 5 部門 + 付録 1 部門に分かれます．\n\n- Basic Virtual DOM 部門\n  - スケジューラの実装\n  - 対応できていないパッチの実装 (主に属性周り)\n  - Fragment の対応\n- Basic Reactivity System 部門\n  - ref api\n  - computed api\n  - watch api\n- Basic Component System 部門\n  - provide/inject\n  - lifecycle hooks\n- Basic Template Compiler 部門\n  - v-on\n  - v-bind\n  - v-for\n  - v-model\n- Basic SFC Compiler 部門\n  - SFC の基本\n  - script setup\n  - compiler macro\n- Web Application Essentials 部門 (付録)\n\n  この部門は付録です．Web 開発において，頻繁に Vue と共に利用されるライブラリの実装をします．\n\n  - store\n  - route\n\n  ここでは上記の 2 つを扱いますが，ぜひ他にも思いつくものがあれば実装してみましょう！\n\n## 方針について\n\nMinimal Example 部門ではかなり細かめに実装の手順について説明してきました．  \nここまで実装してきた皆さんならば，もうかなり本家 Vue のソースコードを読めるようになっているはずです．  \nそこでこれ以降の部では，説明は大まかな方針までにとどめて，実際のコードは本家のコードを読みながら，もしくは自分で考えながら実装していこうと思います．  \n(け，決して，細かく書くのが面倒臭くなってきたとか，そういうことではないですからね！)  \nまあ，本を読んでその通りに実装するのは最初のうちは楽しいですが，ある程度形になってきたら自分でやってみるほうが楽しいですし，より深い理解にもつながるかと思います．\nここから先はこの本はある種のガイドライン程度に捉えて貰って，本編は Vue 本家にあります！\n\n<KawaikoNote variant=\"funny\" title=\"ここからが本番！\">\n\nここまでの知識があれば Vue.js 本家のソースコードも読めるようになっています．\n興味のある部門に進んでもよし，本家のコードを読んでもよし，自由に楽しんでください！\n\n</KawaikoNote>\n"
  },
  {
    "path": "book/online-book/src/ja/20-basic-virtual-dom/010-patch-keyed-children.md",
    "content": "# key属性とパッチレンダリング(Basic Virtual DOM部門スタート)\n\n## 重大なバグ\n\n実は今の chibivue のパッチレンダリングには重大なバグが存在しています．  \nパッチレンダリングの実装をした際に，\n\n> patchChildren に関して、本来は key 属性などを付与して動的な長さの子要素に対応したりしないといけない\n\nと言ったのを覚えているでしょうか．\n\n実際にどのような問題が起こるのか確かめてみましょう．\n現時点での実装だと，patchChildren は以下のような実装になっています．\n\n```ts\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\nこれは，c2(つまり次の vnode)の長さを基準にループを回しています．\nつまり，c1 と c2 が同じ要素の場合にしか基本的には成り立っていないのです．\n\n![Index-based patch with equal lengths](/figures/20-basic-virtual-dom/patch-keyed-children/same-length-index-patch.svg)\n\n例えば，要素が減っていた場合を考えてみましょう．\npatch のループは c2 を基本としているわけなので，4 つめの要素の patch が行われません．\n\n![Deleted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/deleted-child-bug.svg)\n\nこのようになった時，どうなるかというと，単純に 1~3 つ目の要素は更新され，4 つ目は消えず， c1 のものが残ってしまいます．\n\n実際に動作を見てみましょう．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ list: ['a', 'b', 'c', 'd'] })\n    const updateList = () => {\n      state.list = ['e', 'f', 'g']\n    }\n\n    return () =>\n      h('div', { id: 'app' }, [\n        h(\n          'ul',\n          {},\n          state.list.map(item => h('li', {}, [item])),\n        ),\n        h('button', { onClick: updateList }, ['update']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nupdate ボタンを押すと以下のようになるかと思います．\n\n![Stale child bug rendered in the browser](/figures/20-basic-virtual-dom/patch-keyed-children/stale-child-bug-result.png)\n\nlist は`[\"e\", \"f\", \"g\"]`に更新したはずなのに，`d`が残ってしまっています．\n\nそして，実は問題はこれだけではありません．要素が差し込まれた時のことを考えてみましょう．\n現状では，c2 を基準にループを回しているだけなので，以下のようになってしまいます．\n\n![Inserted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-index-bug.svg)\n\nしかし，実際に差し込まれたのは`new element`で，比較は c1,c2 のそれぞれの li 1, li 2, li 3, li 4 同士で行いたいはずです．\n\n![Inserted child handled with keyed matching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg)\n\nこれらの二つの問題に共通して言えることは，「c1 と c2 で同一視したい node が判断できない」ということです．  \nこれを解決するには，要素に key を付与し，その key を元にパッチを行う必要があります．  \nここで，Vue のドキュメントで key 属性についての説明を見てみましょう．\n\n> 特別な属性 key は、主に Vue の Virtual DOM アルゴリズムが新しいノードリストを古いリストに対して差分する際に、vnode を識別するためのヒントとして使用されます。\n\nhttps://ja.vuejs.org/api/built-in-special-attributes.html#key\n\nいかにも，と言ったところです．よく，「v-for の key に index を指定するな」という話があると思いますが，まさに今現時点では暗黙的に key が index になっているがために上記のような問題が発生していました．(c2 の長さを基準に for を回し，その index を元に c1 と patch を行っている)\n\n## key 属性を元に patch しよう\n\nそしてこれらを実装しているのが，`patchKeyedChildren`という関数です．(本家 Vue で探してみましょう．)\n\n方針としては，まず新しい node の key と index のマップを生成します．\n\n```ts\nlet i = 0\nconst l2 = c2.length\nconst e1 = c1.length - 1 // end index of prev node\nconst e2 = l2 - 1 // end index of next node\n\nconst s1 = i // start index of prev node\nconst s2 = i // start index of next node\n\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = s2; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n```\n\n本家の Vue ではこの patchKeyedChildren は５のパートに分かれます．\n\n1. sync from start\n2. sync from end\n3. common sequence + mount\n4. common sequence + unmount\n5. unknown sequence\n\nですが，上の 4 つは最適化のようなものなので，機能的には最後の `unknown sequence` だけで動作可能します．\nなので，まずは`unknown sequence`の部分を読み進めて実装することにしましょう．\n\nまずは，要素の移動のことは忘れて，key を元に VNode を patch していきましょう．\n先ほど作った`keyToNewIndexMap`を利用して，n1 と n2 の組を算出して patch します．\nこの時点で，新しくマウントするものや，アンマウントする必要があればその処理も行ってしまいます．\n\nざっくりいうとこういうこと ↓ (かなり省略しています．詳しくは vuejs/core の renderer.ts を読んでみてください．)\n\n```ts\nconst toBePatched = e2 + 1\nconst newIndexToOldIndexMap = new Array(toBePatched) // 新indexと旧indexとのマップ\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// e1 (旧 len)を元にループ\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // 移動先が見つからなければアンマウント\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // マップ形成\n    patch(prevChild, c2[newIndex] as VNode, container) // パッチ処理\n  }\n}\n\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  if (newIndexToOldIndexMap[i] === 0) {\n    // マップが存在しない(初期値のまま)のであれば新しくマウントするということになる。　(真にはあって、旧にはないということなので)\n    patch(null, nextChild, container, anchor)\n  }\n}\n```\n\n## 要素の移動\n\n## 手法\n\n### Node.insertBefore\n\n現時点では，key の一致のよってそれぞれの要素を更新しているだけなので，移動していた場合は所定の位置に移動させる処理を書かなくてはなりません．\n\nまず，どうやって要素を移動するかについてですが，nodeOps の insert に anchor の指定をします．\nanchor というのは名前の通りアンカーで，runtime-dom に実装した nodeOps を見てもらえればわかるのですが，この insert メソッドは`insertBefore`というメソッドで実装されています．\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // .\n  // .\n  // .\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\nこのメソッドは第二引数に node を渡すことで，その node の直前に insert されるようになります．  \nhttps://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore\n\nこれを利用して実際の DOM を移動させます．\n\n### LIS (Longest Increasing Subsequence)\n\nそして，どのように移動のアルゴリズムを書いていくかですが，こちらはちょっとだけ複雑です．  \nDOM 操作というのは JS を動かすのに比べてかなりコストが高いので，なるべく余計な移動がないように移動回数は最小限にしたいのです．  \nそこで，「最長増加部分列 (Longest Increasing Subsequence)」というものを利用します．  \nこれがどのようなものかというと，名前の通りなんですが，最長の増加部分列です．  \n増加部分列というのは，例えば以下のような配列があったとき，\n\n```\n[2, 4, 1, 7, 5, 6]\n```\n\n増加部分列は以下のようにいくつか存在します．\n\n```\n[2, 4]\n[2, 5]\n.\n.\n[2, 4, 7]\n[2, 4, 5]\n.\n.\n[2, 4, 5, 6]\n.\n.\n[1, 7]\n.\n.\n[1, 5, 6]\n```\n\n増加している部分列です．このうち，最長のものが「最長増加部分列」です．  \nつまり，今回で言うと，`[2, 4, 5, 6]`が最長増加部分列となります．\nそして，Vue ではこの 2, 4, 5, 6 に該当する index を結果の配列として扱っています．　(つまり `[0, 1, 4, 5]`)．\n\nちなみにこんな感じの関数です．\n\n```ts\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice()\n  const result = [0]\n  let i, j, u, v, c\n  const len = arr.length\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i]\n    if (arrI !== 0) {\n      j = result[result.length - 1]\n      if (arr[j] < arrI) {\n        p[i] = j\n        result.push(i)\n        continue\n      }\n      u = 0\n      v = result.length - 1\n      while (u < v) {\n        c = (u + v) >> 1\n        if (arr[result[c]] < arrI) {\n          u = c + 1\n        } else {\n          v = c\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1]\n        }\n        result[u] = i\n      }\n    }\n  }\n  u = result.length\n  v = result[u - 1]\n  while (u-- > 0) {\n    result[u] = v\n    v = p[v]\n  }\n  return result\n}\n```\n\nこれをどう利用するかというと，newIndexToOldIndexMap から最長増加部分列を算出し，それを基準にして，それ以外の node を insertBefore で差し込んでいきます．\n\n## 具体例\n\nちょっとわかりづらいので具体例です．\n\nc1 と c2 という二つの vnode の配列を考えます．c1 が更新前で c2 が更新後で，それぞれが持つ子供はそれぞれ key 属性を持っています．(実際には key 以外の情報を持っています．)\n\n```js\nc1 = [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }]\nc2 = [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }]\n```\n\nこれらを元にまずは keyToNewIndexMap を生成します．(key と，それに対する c2 の index の map)\n※ 以下は先ほど紹介したコードです．\n\n```ts\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = 0; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n\n// keyToNewIndexMap = { a: 0, b: 1, d: 2, c: 3 }\n```\n\n続いて newIndexToOldIndexMap を生成します．\n※ 以下は先ほど紹介したコードです．\n\n```ts\n// 初期化\n\nconst toBePatched = c2.length\nconst newIndexToOldIndexMap = new Array(toBePatched) // 新indexと旧indexとのマップ\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// newIndexToOldIndexMap = [0, 0, 0, 0]\n```\n\n```ts\n// patchをしつつmove用にnewIndexToOldIndexMapを生成する\n\n// e1 (旧 len)を元にループ\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // 移動先が見つからなければアンマウント\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // マップ形成\n    patch(prevChild, c2[newIndex] as VNode, container) // パッチ処理\n  }\n}\n\n// newIndexToOldIndexMap = [1, 2, 4, 3]\n```\n\nそして，得られた newIndexToOldIndexMap から最長増加部分列を取得します．(ここから新実装)\n\n```ts\nconst increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)\n// increasingNewIndexSequence  = [0, 1, 3]\n```\n\n```ts\nj = increasingNewIndexSequence.length - 1\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  const anchor =\n    nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor // ※ parentAnchor はとりあえず引数で受け取った anchor だと思ってもらえれば。\n\n  if (newIndexToOldIndexMap[i] === 0) {\n    // newIndexToOldIndexMap は初期値が 0 なので、0 の場合は古い要素への map が存在しない、つまり新しい要素だというふうに判定している。\n    patch(null, nextChild, container, anchor)\n  } else {\n    // i と increasingNewIndexSequence[j] が一致しなければ move する\n    if (j < 0 || i !== increasingNewIndexSequence[j]) {\n      move(nextChild, container, anchor)\n    } else {\n      j--\n    }\n  }\n}\n```\n\n## 実際に実装してみよう．\n\nさて，方針についてはざっと説明したので実際に `patchKeyedChildren` を実装してみましょう．\nTodo だけまとめておきます．\n\n1. anchor をバケツリレーできるように (move のための insert で使うので)\n2. c2 を元に key と index の map を用意する\n3. key の map を元に c2 の index と c1 の index の map を用意する  \n   この段階で，c1 ベースのループと c2 ベースのループで patch 処理をしておく (move はまだ)\n4. 3 で得た map を元に最長増加部分列を求める\n5. 4 で得た部分列と c2 を元に move する\n\nここからは本家 Vue の実装を見てもらってもいいですし，もちろん chibivue を参考にしてもらっても良いです．\n(おすすめなのは本家 Vue を実際に読みながらです．)\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/010_patch_keyed_children)\n"
  },
  {
    "path": "book/online-book/src/ja/20-basic-virtual-dom/020-bit-flags.md",
    "content": "# ビットによる VNode の表現\n\n## VNode の種類をビットで表現する\n\nVNode にはいろんな種類のものがあります．例えば，今実装しているものだと，以下のようなものです．\n\n- component node\n- element node\n- text node\n- 子要素が text かどうか\n- 子要素が配列かどうか\n\nそして，これから先はさらにいろんな種類の Vnode が追加実装されることでしょう．  \n例えば，slot, keep-alive, suspense, teleport などがそうです．\n\n今のところ，`type === Text` や `typeof type === \"string\"`, `typeof type === \"object\"` などで分岐をおこなっています．\n\nこれらをいちいち判定するのは非効率ですし，本家の実装に倣ってビットで表現することにしてみましょう．  \nVue ではこれらのビットは `ShapeFlags` と呼ばれています．その名の通り，VNode の Shape を表すものです．  \n(厳密には Vue ではこの ShapeFlags と Text や Fragment などの Symbol を使って VNode の種類を判別しています)  \nhttps://github.com/vuejs/core/blob/main/packages/shared/src/shapeFlags.ts\n\nビットフラグがどのようなものかというと，数値の各ビットを特定のフラグとしてみなすというものです．\n\n例として以下のような VNode を考えます．\n\n```ts\nconst vnode = {\n  type: 'div',\n  children: [\n    { type: 'p', children: ['hello'] },\n    { type: 'p', children: ['hello'] },\n  ],\n}\n```\n\n![ShapeFlags pack VNode shape into bits](/figures/20-basic-virtual-dom/bit-flags/shape-flag-overview.svg)\n\nまず，フラグの初期値は 0 です．(簡略化のため 8bit で説明しています．)\n\n```ts\nlet shape = 0b0000_0000\n```\n\nここで，この VNode は element であり，子要素を配列で持っているので ELEMENT というフラグと ARRAY_CHILDREN というフラグが立ちます．\n\n```ts\nshape = shape | ShapeFlags.ELEMENT | ELEMENT.ARRAY_CHILDREN // 0x00010001\n```\n\nこれにより，shape というただ一つの数値で「element でありかつ，子要素を配列を持っている」という情報を表現できました．  \nあとはこれを renderer 等の分岐で使用すれば効率的に VNode の種類を管理できます．\n\n```ts\nif (vnode.shape & ShapeFlags.ELEMENT) {\n  // vnodeがelementの時の処理\n}\n```\n\n今回は，すべての ShapeFlags を実装するわけではないので，練習として以下を実装してみてください．\n\n```ts\nexport const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n```\n\nやることとしては，\n\n- shared/shapeFlags.ts にフラグを定義\n- runtime-core/vnode.ts で vnode に shape を定義\n  ```ts\n  export interface VNode<HostNode = any> {\n    shapeFlag: number\n  }\n  ```\n  を追加し，createVNode などで flag を算出してください．\n- renderer では shape を元に分岐処理を実装する．\n\nです!\n\nなんとこのチャプターの説明は以上です．実際に実装していきましょう !\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/020_bit_flags)\n"
  },
  {
    "path": "book/online-book/src/ja/20-basic-virtual-dom/030-scheduler.md",
    "content": "# スケジューラ\n\n## effect のスケジューリング\n\nまずはこのコードをご覧ください．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      message: 'Hello World',\n    })\n    const updateState = () => {\n      state.message = 'Hello ChibiVue!'\n      state.message = 'Hello ChibiVue!!'\n    }\n\n    return () => {\n      console.log('😎 rendered!')\n\n      return h('div', { id: 'app' }, [\n        h('p', {}, [`message: ${state.message}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nボタンをクリックすると，state.message に対して 2 回 set が起こるので，当然 2 回 trigger が実行されることになります．\nつまりは，2 回 Virtual DOM が算出され，2 回 patch が行われます．\n\n![Effect result before scheduler batching](/figures/20-basic-virtual-dom/scheduler/non-scheduled-effect.png)\n\nしかし，実際に patch 処理を行うのは 2 回目のタイミングだけで十分なはずです．  \nそこで，スケジューラを実装します．スケジューラというのはあるタスクに対する実行順番であったり，実行を管理するものです．\nVue のスケジューラの役割の一つとして，リアクティブな作用をキューで管理し，まとめられるものはまとめる，というのがあります．\n\n## キュー管理によるスケジューリング\n\n具体的にはキュー をもち，ジョブを管理します．ジョブは id を持っており，キューに新しくジョブがエンキューされる際に，既に同一の id を持ったジョブが存在していた場合に上書きしてしまいます．\n\n```ts\nexport interface SchedulerJob extends Function {\n  id?: number\n}\n\nconst queue: SchedulerJob[] = []\n\nexport function queueJob(job: SchedulerJob) {\n  if (\n    !queue.length ||\n    !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)\n  ) {\n    if (job.id == null) {\n      queue.push(job)\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job)\n    }\n    queueFlush()\n  }\n}\n```\n\n肝心のジョブの id ですが，今回の場合はコンポーネント単位でまとめたいので，コンポーネントに uid を持たせるようにして，それらを job の id となるように実装します．\n\nuid といっても単にインクリメントによって得られる識別子です．\n\n## ReactiveEffect とスケジューラ\n\n現在，ReactiveEffect は以下のようなインタフェースになっています．(一部省略)\n\n```ts\nclass ReactiveEffect {\n  public fn: () => T,\n  run() {}\n}\n```\n\nスケジューラの実装に伴って少し変えてみます．  \n現在，作用として fn に関数を登録しているのですが，今回は「能動的に実行する作用」と「受動的に実行される作用」に分けてみます．  \nReactive な作用として扱うものは，作用を設定した側で能動的に実行される場合と，dep に追加された後で，何らかの外部のアクションによって trigger され受動的に実行される場合があります．  \n後者の作用は不特定多数の depsMap に追加され，不特定多数に trigger されるので，スケジューリングの対応が必要です．(逆にいえば能動的(明示的)に呼ぶならばそのような対応は必要ない)\n\n具体例を考えてみましょう．今実際に renderer の setupRenderEffect では以下のような実装があるかと思います．\n\n```ts\nconst effect = (instance.effect = new ReactiveEffect(() => componentUpdateFn))\nconst update = (instance.update = () => effect.run())\nupdate()\n```\n\nここで生成した effect という reactiveEffect はのちに setup の実行によって getter が走った reactive なオブジェクトに track されるわけですが，これは明らかにスケジューリングの実装が必要です．(バラバラにいろんなところから trigger されるため)  \nしかし，ここで`update()`を呼び出していることに関してはそのまま作用を実行するだけでいいはずなので，スケジューリングの実装は必要ありません．  \n「え？　じゃあ componentUpdateFn を直接呼び出せばいいんじゃないの？」と思うかも知れませんが，run の実装をよく思い出してください．componentUpdateFn を呼び出すだけでは activeEffect が設定されません．  \nそこで，「能動的に実行する作用」と「受動的に実行される作用(スケジューラが必要な作用)」を分けてもつように変えてみましょう．\n\nこのチャプターでの最終的なインタフェースとしては，以下のようになります．\n\n```ts\n// ReactiveEffectの第 1 引数が能動的な作用, 第 2 引数が受動的な作用\nconst effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n  queueJob(update),\n))\nconst update: SchedulerJob = (instance.update = () => effect.run())\nupdate.id = instance.uid\nupdate()\n```\n\n実装的には，ReactiveEffect に fn とは別に scheduler という関数をもち，trigger では scheduler を優先して実行するようにします．\n\n```ts\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null\n  );\n}\n```\n\n```ts\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler()\n  } else {\n    effect.run() // なければ通常の作用を実行する\n  }\n}\n```\n\n---\n\nさて，キュー管理によるスケジューリングと作用の分類わけを実際にソースコードを読みながら実装してみましょう !\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/040_scheduler)\n\n## nextTick が欲しい\n\nスケジューラの実装をする際にソースコードを読んだかたは「nextTick ってここで出てくるのか」というのに気づいた方もいるかもしれません．\nまずは今回実現したい課題についてです．こちらのコードをご覧ください．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = () => {\n      state.count++\n\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\nこちらのボタンをクリックしてみてコンソールを覗いてみましょう．\n\n![Old DOM state before nextTick](/figures/20-basic-virtual-dom/scheduler/old-state-dom.png)\n\n`state.count`を更新した後にコンソールに出力しているのに，情報が古くなってしまっています．  \nそれもそのはず，ステートを更新しても瞬時に DOM が更新されるわけではなく，コンソールに出力した段階ではまだ DOM は古い状態のままです．\n\nここで登場するのが nextTick です．\n\nhttps://vuejs.org/api/general.html#nexttick\n\nこの nextTick というのはスケジューラの API で，スケジューラによって DOM に変更が適応されるまで待つことができます．  \nnextTick の実装方法ですが，非常に単純で，スケジューラ内で今 flush しているジョブ(promise)を保持しておいて，それの then に繋ぐだけです．\n\n```ts\nexport function nextTick<T = void>(\n  this: T,\n  fn?: (this: T) => void,\n): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise\n  return fn ? p.then(this ? fn.bind(this) : fn) : p\n}\n```\n\nそのジョブが完了した(promise が resolve された)際に nextTick に渡されたコールバックを実行するということです．(キューにジョブがなければ resolvedPromise の then に繋ぎます)  \n当然，この nextTick 自体も Promise を返すため，開発者インタフェースとしては，コールバックに渡すのもよし，nextTick を await するのもよし，といった感じになっているわけです．\n\n```ts\nimport { createApp, h, reactive, nextTick } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = async () => {\n      state.count++\n\n      await nextTick() // 待つ\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n![DOM state after nextTick](/figures/20-basic-virtual-dom/scheduler/next-tick.png)\n\nさて，実際に今のスケジューラの実装を`currentFlushPromise`を保持しておくような実装に書き換えて，nextTick を実装してみましょう!\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/050_next_tick)\n"
  },
  {
    "path": "book/online-book/src/ja/20-basic-virtual-dom/040-patch-other-attrs.md",
    "content": "# 対応できていない Props のパッチ\n\nこのチャプターでは，現時点で対応できていない Props のパッチを実装していきましょう．  \n以下にはいくつか例として対応対象を挙げますが，各自で足りてない所を本家の実装を読みながら実装してみましょう！  \nそうすればより実用的なものにグレードアップするはずです！\n\n特に新しいことは出てきません．今までやってきたことで十分実装できるはずです．\n\n注目したいのは，runtime-dom/modules の実装です．\n\n## 新旧の比較\n\n現状だと n2 の props を元にしか更新ができていません．  \nn1 と n2 を元に更新しましょう．\n\n```ts\nconst oldProps = n1.props || {}\nconst newProps = n2.props || {}\n```\n\nn1 に存在していて n2n に存在しない props は削除です．  \nまた，両者に存在していても値が変わっていなければ patch する必要はないのでスキップします．\n\n## class / style (注意)\n\nclass と style には複数のバインディング方法があります．\n\n```html\n<p class=\"static property\">hello</p>\n<p :class=\"'dynamic property'\">hello</p>\n<p :class=\"['dynamic', 'property', 'array']\">hello</p>\n<p :class=\"{ dynamic: true, property: true, array: true}\">hello</p>\n<p class=\"static property\" :class=\"'mixed dynamic property'\">hello</p>\n<p style=\"static: true;\" :style=\"{ mixed-dynamic: 'true' }\">hello</p>\n```\n\nこれらを実現するには，Basic Template Compiler 部門で説明する `transform` という概念が必要になります．  \n本家 Vue の設計に則らなければどこに実装してもいいのですが，本書では本家 Vue の設計に則りたいためここではスキップします．\n\n## innerHTML / textContent\n\ninnerHTML と textContent については他の Props と比べて少し特殊です．\\\nというのもこの Prop を持つ要素が子要素を持っていた場合，unmount する必要があります．\n\n例えば以下のようなケースを考えてみましょう．\n\n```ts\nh('div', { innerHTML: '<p>hello</p>' }, [\n  h(SomeComponent, {}, [])\n])\n```\n\nこの場合，`innerHTML` によって div 要素の内容が `<p>hello</p>` に上書きされます．\\\nしかし，children として渡されている `SomeComponent` は既に仮想 DOM 上に存在しており，これを適切にアンマウントしないと以下のような問題が発生します：\n\n- イベントリスナーが解除されない\n- コンポーネントのライフサイクルフック（onUnmounted など）が呼ばれない\n- メモリリークの原因になる\n\nそのため，innerHTML や textContent を設定する際には，既存の子要素をアンマウントする必要があります．\n\n### 実装\n\nまず，`patchProp` の型定義を拡張して，`prevChildren` と `unmountChildren` を受け取れるようにします．\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[], // 追加\n    unmountChildren?: (children: VNode<HostNode>[]) => void, // 追加\n  ): void;\n  // ...\n}\n```\n\n次に，`patchDOMProp` 関数で innerHTML/textContent の処理を実装します．\n\n`~/packages/runtime-dom/modules/props.ts`\n\n```ts\nexport function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === 'innerHTML' || key === 'textContent') {\n    // 既存の子要素がある場合はアンマウント\n    if (prevChildren) {\n      unmountChildren(prevChildren)\n    }\n    el[key] = value == null ? '' : value\n    return\n  }\n\n  // ... (他の props の処理)\n}\n```\n\nそして，`patchProp` から `patchDOMProp` を呼び出す際に，`prevChildren` と `unmountChildren` を渡します．\n\n`~/packages/runtime-dom/patchProp.ts`\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === 'style') {\n    patchStyle(el, prevValue, nextValue)\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue)\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren) // prevChildren, unmountChildren を渡す\n  } else {\n    patchAttr(el, key, nextValue)\n  }\n}\n```\n\n最後に，renderer.ts で `hostPatchProp` を呼び出す際に，適切に引数を渡します．\n\n`~/packages/runtime-core/renderer.ts` の `mountElement` と `patchElement`\n\n```ts\nconst mountElement = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children as VNode[], el, anchor)\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(\n        el,\n        key,\n        null,\n        props[key],\n        vnode.children as VNode[], // 追加\n        unmountChildren, // 追加\n      )\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\nこれで innerHTML や textContent を使用した際に，既存の子要素が適切にアンマウントされるようになります．\n\nここまでのソースコード：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/060_other_props)\n"
  },
  {
    "path": "book/online-book/src/ja/30-basic-reactivity-system/005-reactivity-optimization.md",
    "content": "# Reactivity の最適化\n\n::: info この章について\nこの章では，Vue 3.6 で導入される [alien-signals](https://github.com/stackblitz/alien-signals) ベースの reactivity システムの最適化について解説します．\\\nchibivue の実装もこのアルゴリズムに基づいて更新されています．\n:::\n\n## 背景\n\nVue.js の Reactivity System は，Vue 3.4 で大幅なパフォーマンス最適化が行われました．しかし，Vue 3.5 では Preact に似た pull-based アルゴリズムへの切り替えが行われ，Reactivity System の方向性が変化しました．\n\nそこで，Vue の中心的なコントリビュータである Johnson Chu 氏は，push-pull based な実装をさらに研究するため，独立したプロジェクトとして [alien-signals](https://github.com/stackblitz/alien-signals) を開発しました．\n\nalien-signals は Vue 3.4 の Reactivity System をベースに再実装されたシグナルライブラリで，以下の特徴があります:\n\n- **軽量**: 最小限のメモリ使用量\n- **高速**: パフォーマンスの改善\n- **メモリ効率**: メモリ使用量の削減\n\nこれらの成果は，Vue 3.6 で Vue 本体の Reactivity System に移植されることになりました．\n\n参考: [vuejs/core#12349](https://github.com/vuejs/core/pull/12349)\n\n## Push-Pull リアクティビティアルゴリズム\n\nalien-signals が採用している Push-Pull アルゴリズムについて簡単に説明します．\n\n### Push-based と Pull-based\n\nリアクティビティシステムには大きく分けて 2 つのアプローチがあります:\n\n**Push-based (プッシュ型)**\n\n依存関係が変更されたとき，即座にすべての依存する computed を更新します．\n\n```\nsignal 変更 → すべての computed を即時更新 → effect を実行\n```\n\n利点: 常に最新の値が保証される\n欠点: 使用されない computed も更新される\n\n**Pull-based (プル型)**\n\ncomputed の値が必要になったとき（読み取り時）に初めて計算します．\n\n```\nsignal 変更 → (何もしない) → effect で computed を読む → その時点で計算\n```\n\n利点: 必要な計算のみ実行される\n欠点: 読み取り時のオーバーヘッド\n\n### Push-Pull (ハイブリッド)\n\nalien-signals と Vue 3.6 が採用する Push-Pull アルゴリズムは，両方の利点を組み合わせています:\n\n1. **Push フェーズ**: signal が変更されたとき，依存する computed に「dirty」フラグを設定\n2. **Pull フェーズ**: computed が読み取られたとき，dirty なら再計算\n\n```\nsignal 変更 → dirty フラグを伝播 → effect で computed を読む → dirty なら再計算\n```\n\n![Push-Pull reactivity overview](/figures/30-basic-reactivity-system/reactivity-optimization/push-pull-overview.svg)\n\nこの方式により:\n- 不要な計算を避けられる（Pull の利点）\n- 依存関係の追跡が効率的（Push の利点）\n\n<KawaikoNote variant=\"funny\" title=\"いいとこどり！\">\n\nPush-Pull アルゴリズムは，Push と Pull の両方の良いところを組み合わせた賢いアプローチです．\\\n「変更があったら dirty フラグだけ伝えて，実際の計算は必要になったときにやる」という戦略で，無駄な計算を徹底的に排除しています！\n\n</KawaikoNote>\n\n## alien-signals の基本 API\n\nalien-signals は非常にシンプルな API を提供しています:\n\n```ts\nimport { signal, computed, effect } from 'alien-signals'\n\n// signal: リアクティブな値を作成\nconst count = signal(1)\n\n// 値の読み取り\nconsole.log(count()) // 1\n\n// 値の更新\ncount(2)\n\n// computed: 派生値を作成\nconst double = computed(() => count() * 2)\nconsole.log(double()) // 4\n\n// effect: 副作用を登録\neffect(() => {\n  console.log(`Count is: ${count()}`)\n})\n\ncount(3) // \"Count is: 3\" が出力される\n```\n\nVue の `ref` や `reactive` と比較すると:\n\n| alien-signals | Vue |\n|--------------|-----|\n| `signal(value)` | `ref(value)` |\n| `signal()` で読み取り | `.value` で読み取り |\n| `signal(newValue)` で書き込み | `.value = newValue` で書き込み |\n| `computed(() => ...)` | `computed(() => ...)` |\n| `effect(() => ...)` | `watchEffect(() => ...)` |\n\n## 実装の概要\n\n::: warning\nこの章では alien-signals の実装を完全に移植するのではなく，その概念と基本的な仕組みを解説します．\\\n完全な実装を理解したい場合は，[alien-signals のソースコード](https://github.com/stackblitz/alien-signals)や [Vue 3.6 の PR](https://github.com/vuejs/core/pull/12349) を参照してください．\n:::\n\n<KawaikoNote variant=\"base\" title=\"詳しくは Johnson 氏の解説を！\">\n\nalien-signals のアルゴリズムについて詳しく知りたい方は，作者である Johnson Chu 氏が書いた解説を読むことをお勧めします！\\\n[https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30)\n\n</KawaikoNote>\n\n### 双方向連結リスト\n\nalien-signals の重要な最適化の一つは，依存関係を双方向連結リスト（Doubly Linked List）で管理することです．\n\n従来の Vue の実装では，Set を使って依存関係を管理していました:\n\n```ts\n// 従来の実装\nclass Dep {\n  subscribers = new Set<ReactiveEffect>()\n\n  track() {\n    if (activeEffect) {\n      this.subscribers.add(activeEffect)\n    }\n  }\n\n  trigger() {\n    this.subscribers.forEach(effect => effect.run())\n  }\n}\n```\n\nalien-signals では，連結リストを使用します:\n\n```ts\n// alien-signals スタイル\ninterface Link {\n  dep: Dep\n  sub: Subscriber\n  prevDep: Link | undefined  // 同じ subscriber の前の dep への参照\n  nextDep: Link | undefined  // 同じ subscriber の次の dep への参照\n  prevSub: Link | undefined  // 同じ dep の前の subscriber への参照\n  nextSub: Link | undefined  // 同じ dep の次の subscriber への参照\n}\n```\n\nこの構造により:\n- メモリ使用量の削減（Set のオーバーヘッドを避ける）\n- 依存関係の追加・削除が O(1) で可能\n- GC の負荷軽減\n\n### バージョン管理\n\nもう一つの重要な最適化は，バージョン番号による dirty チェックです:\n\n```ts\nlet globalVersion = 0\n\nfunction triggerRef(ref: Ref) {\n  globalVersion++\n  ref.version = globalVersion\n  // subscribers に dirty を伝播\n}\n\nfunction computedGetter(computed: ComputedRef) {\n  if (computed.globalVersion !== globalVersion) {\n    // 依存関係のいずれかが更新された可能性がある\n    if (checkDirty(computed)) {\n      // 実際に dirty なら再計算\n      computed.value = computed.getter()\n    }\n    computed.globalVersion = globalVersion\n  }\n  return computed.value\n}\n```\n\nグローバルバージョンを使用することで:\n- computed が本当に再計算が必要かどうかを効率的に判定\n- 不要な依存関係の走査を避ける\n\n## chibivue での実装\n\nchibivue では，この alien-signals のアルゴリズムを参考に Reactivity System を実装しています．\n\n主要なファイル:\n- `packages/reactivity/dep.ts` - 依存関係の管理\n- `packages/reactivity/effect.ts` - effect の実装\n- `packages/reactivity/ref.ts` - ref の実装\n- `packages/reactivity/computed.ts` - computed の実装\n\n基本的な構造:\n\n```ts\n// packages/reactivity/dep.ts\nexport interface Link {\n  dep: Dep\n  sub: Subscriber\n  version: number\n  prevDep: Link | undefined\n  nextDep: Link | undefined\n  prevSub: Link | undefined\n  nextSub: Link | undefined\n}\n\nexport class Dep {\n  version = 0\n  link: Link | undefined = undefined\n  subs: Link | undefined = undefined\n\n  track(): Link | undefined {\n    // activeEffect を購読者として登録\n  }\n\n  trigger(): void {\n    // すべての購読者に通知\n  }\n}\n```\n\n```ts\n// packages/reactivity/effect.ts\nexport class ReactiveEffect<T = any> implements Subscriber {\n  deps: Link | undefined = undefined\n  depsTail: Link | undefined = undefined\n\n  run(): T {\n    // effect 関数を実行し，依存関係を収集\n  }\n}\n```\n\nこの章以降のチャプターでは，この最適化された Reactivity System をベースに実装を進めていきます．\n\n<KawaikoNote variant=\"base\" title=\"次のステップへ\">\n\nalien-signals の概念を理解できましたか？\\\n連結リストやバージョン管理は最初は難しく感じるかもしれませんが，実際にコードを書いていくうちに自然と理解できるようになります．\\\n次のチャプターでは，この最適化された仕組みの上に ref や computed を実装していきましょう！\n\n</KawaikoNote>\n\n## まとめ\n\n- Vue 3.6 では alien-signals ベースの最適化された Reactivity System が導入される\n- Push-Pull アルゴリズムにより，効率的な dirty チェックと遅延評価を実現\n- 双方向連結リストによる依存関係管理でメモリ効率が向上\n- バージョン番号による dirty チェックで不要な再計算を回避\n\n次のチャプターからは，この最適化された Reactivity System の上に，ref や computed などの API を実装していきます．\n\n## 参考リンク\n\n- [stackblitz/alien-signals](https://github.com/stackblitz/alien-signals) - alien-signals 公式リポジトリ\n- [alien-signals アルゴリズムの詳細解説](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30) - 作者の Johnson Chu 氏による詳細な解説\n- [vuejs/core#12349](https://github.com/vuejs/core/pull/12349) - Vue 3.6 への移植 PR\n- [Vue 3.6 Alien Signals の解説](https://medium.com/@revanthkumarpatha/mastering-vue-3-6s-alien-signals-practical-examples-and-use-cases-7df02a159d8a) - Medium 記事\n"
  },
  {
    "path": "book/online-book/src/ja/30-basic-reactivity-system/010-ref-api.md",
    "content": "# ref api (Basic Reactivity System 部門スタート)\n\n::: tip 前提知識\nこのチャプターを読む前に，[Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization) を読んで，alien-signals ベースの最適化されたリアクティビティシステムの概念を理解しておくことをお勧めします．\n:::\n\n## ref api のおさらい (と実装)\n\nVue.js には Reactivity に関する様々な api がありますが，中でも ref はあまりに有名です．  \n公式ドキュメントの方でも Reactivity Core という名目で，しかも一番最初に紹介されています．  \nhttps://vuejs.org/api/reactivity-core.html#ref\n\nところで，ref とはどのような API でしょうか？\n公式ドキュメントによると，\n\n> The ref object is mutable - i.e. you can assign new values to .value. It is also reactive - i.e. any read operations to .value are tracked, and write operations will trigger associated effects.\n\n> If an object is assigned as a ref's value, the object is made deeply reactive with reactive(). This also means if the object contains nested refs, they will be deeply unwrapped.\n\n(引用: https://vuejs.org/api/reactivity-core.html#ref)\n\nとあります．\n\n要するに，ref object というのは 2 つの性質を持ちます．\n\n- value プロパティに対する get/set は track/trigger が呼ばれる\n- value プロパティにオブジェクトが割り当てられた際は value プロパティの値は reactive オブジェクトになる\n\nコードベースで説明しておくと，\n\n```ts\nconst count = ref(0)\ncount.value++ // effect (性質 1 )\n\nconst state = ref({ count: 0 })\nstate.value = { count: 1 } // effect (性質 1 )\nstate.value.count++ // effect (性質 2 )\n```\n\nということです．\n\nref と reactive の区別がつかないうちは，`ref(0)`と`reactive({ value: 0 })` の区別をごちゃごちゃにしてしまいがちですが，上記の 2 つの性質から考えると全く意味が別だということがわかります．\nref は `{ value: x }` という reactive オブジェクトを生成するわけではありません．value に対する get/value の track/trigger は ref の実装が行い，x に当たる部分がオブジェクトの場合は reactive オブジェクトにするということです．\n\n実装のイメージ的にはこういう感じです．\n\n```ts\nclass RefImpl<T> {\n  private _value: T\n  public dep?: Dep = undefined\n\n  get value() {\n    trackRefValue(this)\n  }\n\n  set value(newVal) {\n    this._value = toReactive(v)\n    triggerRefValue(this)\n  }\n}\n\nconst toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value\n```\n\n実際にソースコードを見ながら ref を実装してみましょう！  \n色々な関数や class がありますが，とりあえず RefImpl クラスと ref 関数を中心的に読んでもらえればよいかと思います．\n\n以下のようなソースコードが動かせるようになれば OK です！\n(※注: template のコンパイラは別で ref に対応する必要があるので動きません)\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['Increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/010_ref)\n\n## shallowRef\n\nさて，続けてどんどん ref 周りの api を実装していきます．  \n先ほど，ref の性質として「value プロパティにオブジェクトが割り当てられた際は value プロパティの値は reactive オブジェクトになる」というものを紹介しましたが，この性質を持たないのが shallowRef です．\n\n> Unlike ref(), the inner value of a shallow ref is stored and exposed as-is, and will not be made deeply reactive. Only the .value access is reactive.\n\n(引用: https://vuejs.org/api/reactivity-advanced.html#shallowref)\n\nやることは非常に単純で，RefImpl の実装はそのまま使い，`toReactive`の部分をスキップします．  \nソースコードを読みながら実装していきましょう！\n\n以下のようなソースコードが動かせるようになれば OK です！\n\n```ts\nimport { createApp, h, shallowRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // clickしても描画は更新されない\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n### triggerRef\n\n前述の通り，shallow ref が持つ value は reactive オブジェクトではないので，変更を加えてもエフェクトがトリガーされることはありません．  \nしかし，value 自体はオブジェクトなので変更されています．  \nそこで，強制的にトリガーさせる api が存在します．それが triggerRef です．\n\nhttps://vuejs.org/api/reactivity-advanced.html#triggerref\n\n```ts\nimport { createApp, h, shallowRef, triggerRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n    const forceUpdate = () => {\n      triggerRef(state)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // clickしても描画は更新されない\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n\n        h(\n          'button', // 描画が今の state.value.count が持つ値に更新される\n          { onClick: forceUpdate },\n          ['force update !'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/020_shallow_ref)\n\n## toRef\n\ntoRef は reactive オブジェクトのプロパティへの ref を生成する api です．\n\nhttps://vuejs.org/api/reactivity-utilities.html#toref\n\nprops の特定のプロパティを ref に変換したりする際によく利用します．\n\n```ts\nconst count = toRef(props, 'count')\nconsole.log(count.value)\n```\n\ntoRef によって作られた ref は元の reactive オブジェクトと同期され，\nこの ref に変更を加えると元の reactive オブジェクトも更新され，元の reactive オブジェクトに変更があるとこの ref も更新されます.\n\n```ts\nimport { createApp, h, reactive, toRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const stateCountRef = toRef(state, 'count')\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`state.count: ${state.count}`]),\n        h('p', {}, [`stateCountRef.value: ${stateCountRef.value}`]),\n        h('button', { onClick: () => state.count++ }, ['updateState']),\n        h('button', { onClick: () => stateCountRef.value++ }, ['updateRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nソースコードを読みつつ実装していきましょう！\n\n※ v3.3 からは toRef に normalization の機能が追加されました．chibivue ではこの機能を実装していません．  \n詳しくは公式ドキュメントのシグネチャをチェックしてみてください! (https://vuejs.org/api/reactivity-utilities.html#toref)\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/030_to_ref)\n\n## toRefs\n\nreactive オブジェクトの全てのプロパティの ref を生成します．\n\nhttps://vuejs.org/api/reactivity-utilities.html#torefs\n\n```ts\nimport { createApp, h, reactive, toRefs } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ foo: 1, bar: 2 })\n    const stateAsRefs = toRefs(state)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`[state]: foo: ${state.foo}, bar: ${state.bar}`]),\n        h('p', {}, [\n          `[stateAsRefs]: foo: ${stateAsRefs.foo.value}, bar: ${stateAsRefs.bar.value}`,\n        ]),\n        h('button', { onClick: () => state.foo++ }, ['update state.foo']),\n        h('button', { onClick: () => stateAsRefs.bar.value++ }, [\n          'update stateAsRefs.bar.value',\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nこちらは toRef の実装を使って簡単に実装できるかと思います．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/040_to_refs)\n"
  },
  {
    "path": "book/online-book/src/ja/30-basic-reactivity-system/020-computed-watch.md",
    "content": "# computed / watch api\n\n::: warning\nここで説明される実装は現在執筆中の [Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization) 以前のものになります．\\\n[Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization)　が完成次第，このチャプターの内容もそれに沿うように変更されます．\n:::\n\n## computed のおさらい (と実装)\n\n前のチャプターで ref 系の api を実装しました．続いては computed です．  \nhttps://vuejs.org/api/reactivity-core.html#computed\n\ncomputed には読み取り専用と書き込み可能の 2 つのシグネチャがあります．\n\n```ts\n// read-only\nfunction computed<T>(\n  getter: () => T,\n  // see \"Computed Debugging\" link below\n  debuggerOptions?: DebuggerOptions,\n): Readonly<Ref<Readonly<T>>>\n\n// writable\nfunction computed<T>(\n  options: {\n    get: () => T\n    set: (value: T) => void\n  },\n  debuggerOptions?: DebuggerOptions,\n): Ref<T>\n```\n\n本家の実装は短いながらに少しだけ複雑なので，まずはシンプルな構成を考えてみましょう．\n\n最も簡単な方法として思いつくのは，value を get する際に毎度コールバックを発火するという手法でしょう．\n\n```ts\nexport class ComputedRefImpl<T> {\n  constructor(private getter: ComputedGetter<T>) {}\n\n  get value() {\n    return this.getter()\n  }\n\n  set value() {}\n}\n```\n\nしかしこれでは computed というか，ただ関数を呼んでいるだけです (そりゃそう．特に何も嬉しくないですね，残念．)\n\n実際には依存関係を追跡し，その値が変更されたときに再計算したいわけです．\n\nこれをどのように実現しているかというと，\\_dirty フラグの書き換えをスケジューラのジョブとして実行するような仕組みになっています．\n\\_dirty フラグというのはいわば「再計算する必要があるか?」という状態を持つフラグで，依存によって trigger された場合にこのフラグを書き換えます．\n\nイメージ的には以下のようなものです．\n\n```ts\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined\n  private _value!: T\n  public readonly effect: ReactiveEffect<T>\n  public _dirty = true\n\n  constructor(getter: ComputedGetter<T>) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true\n      }\n    })\n  }\n\n  get value() {\n    trackRefValue(this)\n    if (this._dirty) {\n      this._dirty = false\n      this._value = this.effect.run()\n    }\n    return this._value\n  }\n}\n```\n\ncomputed は実は遅延評価のような性質を持っており，再計算が必要な場合は値が読み取られる際に初めて再計算されます．\nそしてこのフラグを true に書き換え関数は不特定多数の依存に trigger されるため，ReactiveEffect の scheduler として登録します．\n\n基本的な流れはこのような感じです．実装するにあたって，加えて注意する点がいくつかあるので以下にまとめておきます．\n\n- \\_dirty フラグを true に書き換えた段階で自信が持つ依存関係は trigger してしまう\n  ```ts\n  if (!this._dirty) {\n    this._dirty = true\n    triggerRefValue(this)\n  }\n  ```\n- computed も分類的には`ref`なので，`__v_isRef`を true にマークしておく\n- setter を実装したかったら最後に実装する．まずは算出できるようになることを目指す\n\nさて，これで準備は整ったので実際に実装してみましょう！  \n以下のようなコードが期待通りに動けば OK です！　(それぞれ，依存している computed だけが発火することを確認してください！　)\n\n```ts\nimport { computed, createApp, h, reactive, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = reactive({ value: 0 })\n    const count2 = reactive({ value: 0 })\n    const double = computed(() => {\n      console.log('computed')\n      return count.value * 2\n    })\n    const doubleDouble = computed(() => {\n      console.log('computed (doubleDouble)')\n      return double.value * 2\n    })\n\n    const countRef = ref(0)\n    const doubleCountRef = computed(() => {\n      console.log('computed (doubleCountRef)')\n      return countRef.value * 2\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('p', {}, [`count2: ${count2.value}`]),\n        h('p', {}, [`double: ${double.value}`]),\n        h('p', {}, [`doubleDouble: ${doubleDouble.value}`]),\n        h('p', {}, [`doubleCountRef: ${doubleCountRef.value}`]),\n        h('button', { onClick: () => count.value++ }, ['update count']),\n        h('button', { onClick: () => count2.value++ }, ['update count2']),\n        h('button', { onClick: () => countRef.value++ }, ['update countRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/050_computed)\n(setter 込みはこちら):  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/060_computed_setter)\n\n## Watch の実装\n\nhttps://vuejs.org/api/reactivity-core.html#watch\n\nwatch にもいろんな形式の api があります．まずは最も単純な，getter 関数によって監視するような api を実装してみましょう．\nまずは，以下のようなコードが動くことを目指します．\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    watch(\n      () => state.count,\n      () => alert('state.count was changed!'),\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: () => state.count++ }, ['update state']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nwatch の実装は reactivity ではなく，runtime-core の方に実装していきます (apiWatch.ts)．\n\nさまざまな api が混在しているので，少し複雑に見えますが，範囲を絞ればとても単純なことです．  \n目標とする api(watch 関数)のシグネチャを以下に実装しておくので，是非実装してみてください．  \n今までリアクティビティの知識を培ってきたみなさんなら実装できると思います！\n\n```ts\nexport type WatchEffect = (onCleanup: OnCleanup) => void\n\nexport type WatchSource<T = any> = () => T\n\ntype OnCleanup = (cleanupFn: () => void) => void\n\nexport function watch<T>(\n  source: WatchSource<T>,\n  cb: (newValue: T, oldValue: T) => void,\n) {\n  // TODO:\n}\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/070_watch)\n\n## watch の その他の api\n\nベースができてしまえば，後は拡張するだけです．これも特に解説は必要ないでしょう．\n\n- ref の監視\n  ```ts\n  const count = ref(0)\n  watch(count, () => {\n    /** some effects */\n  })\n  ```\n- 複数の source の監視\n\n  ```ts\n  const count = ref(0)\n  const count2 = ref(0)\n  const count3 = ref(0)\n  watch([count, count2, count3], () => {\n    /** some effects */\n  })\n  ;``\n  ```\n\n- immediate\n\n  ```ts\n  const count = ref(0)\n  watch(\n    count,\n    () => {\n      /** some effects */\n    },\n    { immediate: true },\n  )\n  ```\n\n- deep\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(\n    () => state,\n    () => {\n      /** some effects */\n    },\n    { deep: true },\n  )\n  ```\n\n- reactive object\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(state, () => {\n    /** some effects */\n  }) // automatically in deep mode\n  ```\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/080_watch_api_extends)\n\n## watchEffect\n\nhttps://vuejs.org/api/reactivity-core.html#watcheffect\n\nwatch の実装を使えば watchEffect の実装は簡単です．\n\n```ts\nconst count = ref(0)\n\nwatchEffect(() => console.log(count.value))\n// -> logs 0\n\ncount.value++\n// -> logs 1\n```\n\nイメージてには immediate のような実装をすれば OK です．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/090_watch_effect)\n\n---\n\n※ クリーンアップについては別のチャプターで行います．\n"
  },
  {
    "path": "book/online-book/src/ja/30-basic-reactivity-system/030-reactive-proxy-handlers.md",
    "content": "# 様々な Reactive Proxy Handler\n\n::: warning\nここで説明される実装は現在執筆中の [Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization) 以前のものになります．\\\n[Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization)　が完成次第，このチャプターの内容もそれに沿うように変更されます．\n:::\n\n## reactive にしたくないオブジェクト\n\nさて，ここでは現状のリアクティビティシステムのある問題について解決していきます．  \nまずは以下のコードを動かしてみてください．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nコンソールを見てみると，以下のようになっていることが観測できるかと思います．\n\n![Reactive HTML element console output](/figures/30-basic-reactivity-system/reactive-proxy-handlers/reactive-html-element.png)\n\nここで，focus をする処理を加えてみましょう．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nなんと，エラーになってしまいます．\n\n![Focus result in a reactive HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-reactive-html-element.png)\n\nこれの原因としては，document.getElementById によって取得した要素自体を元に Proxy を生成してしまっているためです．\n\nProxy を生成してしまうと値は当然元のオブジェクトではなく Proxy になってしまいますから，HTML 要素としての機能が失われてしまっているのです．\n\n## reactive Proxy を生成する前にオブジェクトを判定する．\n\n判定方法はとてもシンプルです．`Object.prototype.toString`を利用します．\n先ほどのコードで，Object.prototype.toString を使うと HTMLInputElement はどのように判定されるかみてみましょう．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value?.toString())\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n![HTMLElement toString result](/figures/30-basic-reactivity-system/reactive-proxy-handlers/element-to-string.png)\n\nこのようにしてどのようなオブジェクトなのかというのを知ることができます．ややハードコードですが，この判定関数を一般化します．\n\n```ts\n// shared/general.ts\nexport const objectToString = Object.prototype.toString // isMapやisSetなどで既出\nexport const toTypeString = (value: unknown): string =>\n  objectToString.call(value)\n\n// 今回追加する関数\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1)\n}\n```\n\nslice しているのは，`[Object hoge]`の hoge に当たる文字列を取得するためです．\n\nそして，reactive toRawType によってオブジェクトの種類を判別し，分岐していきましょう．  \nHTMLInput の場合は Proxy の生成をスキップするようにします．\n\nreactive.ts の方で，rawType を取得し，reactive のターゲットとなるオブジェクトのタイプを判定します．\n\n```ts\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value)\n    ? TargetType.INVALID\n    : targetTypeMap(toRawType(value))\n}\n```\n\n```ts\nexport function reactive<T extends object>(target: T): T {\n  const targetType = getTargetType(target)\n  if (targetType === TargetType.INVALID) {\n    return target\n  }\n\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\nこれで先ほどのフォーカスのコードが動くようになったはずです！\n\n![Focus result in a plain HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-element.png)\n\n## TemplateRefs を実装してみる\n\nせっかく Ref に HTML 要素を入れられるようになったので，TemplateRef を実装してみましょう．\n\nref は ref 属性を利用することで template への参照を取ることができます．\n\nhttps://vuejs.org/guide/essentials/template-refs.html\n\n目標は以下のようなコードが動くようになることです．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { ref: inputRef }, []),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでやってきたみなさんならば，実装方法はもう見えてるかと思います．\nそう，VNode に ref を持たせて render 時に値をぶち込んでやればいいわけです．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  key: string | number | symbol | null\n  ref: Ref | null // これ\n  // .\n  // .\n}\n```\n\n本家の実装でいうと，setRef という関数です．探して，読んで，実装をしてみましょう！  \n本家の方では配列で ref を持ったり，$ref でアクセスできるようにしたりと色々複雑になっていますが，とりあえず上記のコードが動く程度のものを目指してみましょう．\n\nついでに，component だった場合は component の setupContext を ref に代入してあげましょう．  \n(※ ここは本当はコンポーネントの proxy を渡すべきなんですが，まだ未実装のため setupContext ということにしています．)\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst Child = {\n  setup() {\n    const action = () => alert('clicked!')\n    return { action }\n  },\n\n  template: `<button @click=\"action\">action (child)</button>`,\n}\n\nconst app = createApp({\n  setup() {\n    const childRef = ref<any>(null)\n    const childAction = () => {\n      childRef.value?.action()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('div', {}, [\n          h(Child, { ref: childRef }, []),\n          h('button', { onClick: childAction }, ['action (parent)']),\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/110_template_refs)\n\n## key が増減するオブジェクトに対応する\n\n実は，今の実装では key が増減するオブジェクトに対応できていません．\nこの，「key が増減するオブジェクト」と言うのは配列も含みます．  \n要するに，以下のようなコンポーネントが正常に動作しません．\n\n```ts\nconst App = {\n  setup() {\n    const array = ref<number[]>([])\n    const mutateArray = () => {\n      array.value.push(Date.now()) // trigger しても何も effect がない (この時、set の key は \"0\")\n    }\n\n    const record = reactive<Record<string, number>>({})\n    const mutateRecord = () => {\n      record[Date.now().toString()] = Date.now() // trigger しても何も effect がない (key 新しく設定された key)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`array: ${JSON.stringify(array.value)}`]),\n        h('button', { onClick: mutateArray }, ['update array']),\n\n        h('p', {}, [`record: ${JSON.stringify(record)}`]),\n        h('button', { onClick: mutateRecord }, ['update record']),\n      ])\n  },\n}\n```\n\nこれを解決するにはどうしたら良いでしょうか?\n\n### 配列の場合\n\n配列もいってしまえばオブジェクトなので，新しい要素を追加するとその index が key として Proxy の set の handler に入ってきます．\n\n```ts\nconst p = new Proxy([], {\n  set(target, key, value, receiver) {\n    console.log(key) // ※\n    Reflect.set(target, key, value, receiver)\n    return true\n  },\n})\n\np.push(42) // 0\n```\n\nしかしこれらの key をそれぞれ track するわけにはいきません．\nそこで，length を track することで配列の変更をトリガーするようにします．\n\nlength を track すると言いましたが，実はすでに track されるようになっています．\n\n以下のようなコードをブラウザなどで実行してみると JSON.stringify で配列を文字列化した際に length が呼ばれていることがわかります．\n\n```ts\nconst data = new Proxy([], {\n  get(target, key) {\n    console.log('get!', key)\n    return Reflect.get(target, key)\n  },\n})\n\nJSON.stringify(data)\n// get! length\n// get! toJSON\n```\n\nつまりすでに length には effect が登録されているので，あとは index が set された時に，この effect を取り出して trigger してあげれば良いわけです．\n\n入ってきた key が index かどうかを判定して，index だった場合は length の effect を trigger するようにします．\n他にも dep がある可能性はもちろんあるので， deps という配列に切り出して，effect を詰めてまとめて trigger してあげます．\n\n```ts\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  // これ\n  if (isIntegerKey(key)) {\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n```ts\n// shared/general.ts\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) &&\n  key !== 'NaN' &&\n  key[0] !== '-' &&\n  '' + parseInt(key, 10) === key\n```\n\nこれで配列の場合は動くようになりました．\n\n### オブジェクト(レコード)の場合\n\n続いてはオブジェクトですが，配列とは違い length という情報は持っていません．\n\nこれは一工夫します．  \n`ITERATE_KEY` というシンボルを用意してこれを配列の時の length のように使います．  \n何を言っているのかよく分からないかもしれませんが，depsMap はただの Map なので，別に勝手に用意したものを key として使っても問題ありません．\n\n配列の時と少し順番は変わりますが，まず trigger から考えてみます．\nあたかも `ITERATE_KEY` というものが存在し，そこに effect が登録されているかのような実装をしておけば OK です．\n\n```ts\nexport const ITERATE_KEY = Symbol()\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  if (!isArray(target)) {\n    // 配列でない場合は、ITERATE_KEY に登録された effect を trigger する\n    deps.push(depsMap.get(ITERATE_KEY))\n  } else if (isIntegerKey(key)) {\n    // new index added to array -> length changes\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n問題は，この `ITERATE_KEY` に対してどう effect をトラックするかです．\n\nここで， `ownKeys` と言う Proxy ハンドラを利用します．\n\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys\n\nownKeys は `Object.keys()` や `Reflect.ownKeys()` などで呼ばれますが，実は `JSON.stringify` でも呼ばれます．\n\n試しに以下のコードをブラウザなどで動かしてみるとそれを確認できます．\n\n```ts\nconst data = new Proxy(\n  {},\n  {\n    get(target, key) {\n      return Reflect.get(target, key)\n    },\n    ownKeys(target) {\n      console.log('ownKeys!!!')\n      return Reflect.ownKeys(target)\n    },\n  },\n)\n\nJSON.stringify(data)\n```\n\nあとはこれを利用して `ITERATE_KEY` を track すれば良いわけです．\n配列だった場合は，必要ないのでテキトーに length を track してあげます．\n\n```ts\nexport const mutableHandlers: ProxyHandler<object> = {\n  // .\n  // .\n  ownKeys(target) {\n    track(target, isArray(target) ? 'length' : ITERATE_KEY)\n    return Reflect.ownKeys(target)\n  },\n}\n```\n\nこれでキーが増減するオブジェクトに対応でき多はずです！\n\n## Collection 系の組み込みオブジェクトに対応する\n\n今，reactive.ts の実装を見てみると，Object と Array のみを対象としています．\n\n```ts\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n```\n\nVue.js では，これらに加え，Map, Set, WeakMap, WeakSet に対応しています．\n\nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/reactive.ts#L43C1-L56C2\n\nそして，これらのオブジェクトは別の Proxy ハンドラとして実装されています．それが，`collectionHandlers`と呼ばれるものです．\n\nここでは，この collectionHandlers を実装し，以下のようなコードが動くことを目指します．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ map: new Map(), set: new Set() })\n\n    return () =>\n      h('div', {}, [\n        h('h1', {}, [`ReactiveCollection`]),\n\n        h('p', {}, [\n          `map (${state.map.size}): ${JSON.stringify([...state.map])}`,\n        ]),\n        h('button', { onClick: () => state.map.set(Date.now(), 'item') }, [\n          'update map',\n        ]),\n\n        h('p', {}, [\n          `set (${state.set.size}): ${JSON.stringify([...state.set])}`,\n        ]),\n        h('button', { onClick: () => state.set.add('item') }, ['update set']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\ncollectionHandlers では，add や set, delete といったメソッドの getter にハンドラを実装します．  \nそれらを実装しているのが collectionHandlers.ts です．  \nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/collectionHandlers.ts#L0-L1  \nTargetType を判別し，collection 型の場合 h にはこのハンドラを元に Proxy を生成します．  \n実際に実装してみましょう!\n\n注意点としては，Reflect の receiver に target 自身を渡す点で，target 自体に Proxy が設定されていた場合に無限ループになることがある点です．  \nこれを回避するために target に対して生のデータも持たせておくような構造に変更し，Proxy のハンドラを実装するにあたってはこの生データを操作するように変更します．\n\n```ts\nexport const enum ReactiveFlags {\n  RAW = '__v_raw',\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any\n}\n```\n\n厳密には今までの通常の reactive ハンドラでもこの実装をしておくべきだったのですが，今までは特に問題なかったという点と余計な説明をなるべく省くために省略していました．  \ngetter に入ってきたの key が ReactiveFlags.RAW の場合には Proxy ではなく生のデータを返すような実装にしてみましょう．\n\nそれに伴って，target から再帰的に生データをとり，最終的に全てが生の状態のデータを取得する toRaw という関数も実装しています．\n\n```ts\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW]\n  return raw ? toRaw(raw) : observed\n}\n```\n\nちなみに，この toRaw 関数は API としても提供されている関数です．\n\nhttps://ja.vuejs.org/api/reactivity-advanced.html#toraw\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/120_proxy_handler_improvement)\n"
  },
  {
    "path": "book/online-book/src/ja/30-basic-reactivity-system/040-effect-scope.md",
    "content": "# Effect のクリーンアップと Effect Scope\n\n::: warning\nここで説明される実装は現在執筆中の [Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization) 以前のものになります．\\\n[Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization)　が完成次第，このチャプターの内容もそれに沿うように変更されます．\n:::\n\n## ReactiveEffect のクリーンアップ\n\n私たちは今まで登録した effect のクリーンアップを行っていません．ReactiveEffect にクリーンアップの処理を追加してみましょう．\n\nReactiveEffect に stop というメソッドを実装します．  \nReactiveEffect に自身がアクティブかどうかというフラグを持たせ，stop メソッドではそれを false に切り替えつつ，deps の削除を行います．\n\n```ts\nexport class ReactiveEffect<T = any> {\n  active = true // 追加\n  //.\n  //.\n  //.\n  stop() {\n    if (this.active) {\n      this.active = false\n    }\n  }\n}\n```\n\nついでに，cleanUp 時に行いたい処理を登録できるような hooks の実装と，activeEffect が自身だった場合のハンドリングを追加しておきます．\n\n```ts\nexport class ReactiveEffect<T = any> {\n  private deferStop?: boolean // 追加\n  onStop?: () => void // 追加\n  parent: ReactiveEffect | undefined = undefined // 追加 (finallyで参照したいので)\n\n  run() {\n    if (!this.active) {\n      return this.fn() // active が false な場合は単に関数を実行するだけに\n    }\n\n    try {\n      this.parent = activeEffect\n      activeEffect = this\n      const res = this.fn()\n      return res\n    } finally {\n      activeEffect = this.parent\n      this.parent = undefined\n      if (this.deferStop) {\n        this.stop()\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      // activeEffectが自身だった場合はrunが終わった最後にstopするようにフラグを立てる\n      this.deferStop = true\n    } else if (this.active) {\n      // ...\n      if (this.onStop) {\n        this.onStop() // 登録されたフックを実行\n      }\n      // ...\n    }\n  }\n}\n```\n\nReactiveEffect にクリーンアップ処理が登録できたので，ついでに watch のクリーンアップ関数を実装してみましょう．\n\n以下のようなコードが動くようになれば OK です．\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    const unwatch = watch(\n      () => state.count,\n      (newValue, oldValue, cleanup) => {\n        alert(`New value: ${newValue}, old value: ${oldValue}`)\n        cleanup(() => alert('Clean Up!'))\n      },\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, [`increment`]),\n        h('button', { onClick: unwatch }, [`unwatch`]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/130_cleanup_effects)\n\n## Effect Scope とは\n\nさて，effect をクリーンアップできるようになったわけなので，コンポーネントがアンマウントされた際にも不要な effect はクリーンアップしたいものです．\nしかし，watch にしろ computed にしろ，たくさんの effect を収集するのは少々めんどくさいです．\n愚直に実装しようと思うと，以下のようになってしまいます．\n\n```ts\nlet disposables = []\n\nconst counter = ref(0)\n\nconst doubled = computed(() => counter.value * 2)\ndisposables.push(() => stop(doubled.effect))\n\nconst stopWatch = watchEffect(() => console.log(`counter: ${counter.value}`))\ndisposables.push(stopWatch)\n```\n\n```ts\n// cleanup effects\ndisposables.forEach(f => f())\ndisposables = []\n```\n\nこのような管理はめんどくさいですし，必ずどこかでミスります．\n\nそこで，Vue には EffectScope という機構があります．\nhttps://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md\n\nイメージ的には 1 インスタンス 1 EffectScope を持つ感じで，具体的には以下のようなインタフェースになっています．\n\n```ts\nconst scope = effectScope()\n\nscope.run(() => {\n  const doubled = computed(() => counter.value * 2)\n\n  watch(doubled, () => console.log(doubled.value))\n\n  watchEffect(() => console.log('Count: ', doubled.value))\n})\n\n// to dispose all effects in the scope\nscope.stop()\n```\n\n引用元: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md#basic-example\n\nそして，この EffectScope というのはユーザー向けの API としても公開されています．  \nhttps://ja.vuejs.org/api/reactivity-advanced.html#effectscope\n\n## EffectScope の実装\n\n先ほども言ったように，1 インスタンスに 1 EffectScope を持つような形をとります．\n\n```ts\nexport interface ComponentInternalInstance {\n  scope: EffectScope\n}\n```\n\nそして，アンマウント時に収集しておいた effect を stop します\n\n```ts\nconst unmountComponent = (...) => {\n  // .\n  // .\n  const { scope } = instance;\n  scope.stop();\n  // .\n  // .\n}\n```\n\nEffectScope の構造ですが，activeEffectScope という現在 active な EffectScope を指す変数を一つ持ち，EffectScope に実装された on/off/run/stop というメソッドでその状態を管理します．  \non/off メソッドは，自身を activeEffectScope として持ち上げたり，持ち上げた状態を元に戻す(元々の EffectScope に戻す)といったことを行います．  \nそして，ReactiveEffect が生成される際は，effect を activeEffectScope に登録するようにします．\n\n少しわかりづらいので，イメージをソースコードで書くと，\n\n```ts\ninstance.scope.on()\n\n/** computed や watch などの何らかの ReactiveEffect が生成される */\nsetup()\n\ninstance.scope.off()\n```\n\nこのようにすることで，生成された effect を instance の EffectScope に収集しておくことができるというわけです．  \nあとは，この effect の stop メソッドを発火すると全ての effect のクリーンアップを行うことができます．\n\n基本的な原理については理解できたはずなので，実際にソースコードを読みながら実装してみましょう！\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/140_effect_scope)\n"
  },
  {
    "path": "book/online-book/src/ja/30-basic-reactivity-system/050-other-apis.md",
    "content": "# その他の reactivity api\n\n::: warning\nここで説明される実装は現在執筆中の [Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization) 以前のものになります．\\\n[Reactivity の最適化](/ja/30-basic-reactivity-system/005-reactivity-optimization)　が完成次第，このチャプターの内容もそれに沿うように変更されます．\n:::\n\n## その他の reactivity api を実装してみましょう !\n\n- customRef\n- readonly\n- shallowReactive\n- unref\n- isProxy\n- isReactive\n- isReadonly\n\nここまでやってきた方なら説明がなくてもソースコードを読みながら実装できるはずです！\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/150_other_apis)\n"
  },
  {
    "path": "book/online-book/src/ja/40-basic-component-system/010-lifecycle-hooks.md",
    "content": "# ライフサイクルフック (Basic Component System 部門スタート)\n\n## ライフサイクルフックを実装しよう\n\nライフサイクルフックの実装はとても簡単です．  \nComponentInternalInstance に関数を登録して，render 時に所定のタイミングで実行してあげるだけです．  \nAPI 自体は runtime-core/apiLifecycle.ts に実装していきます．\n\n一点，注意するべきところがあるとすれば，onMounted/onUnmounted/onUpdated に関してはスケジューリングを考えなければならない点です．  \n登録された関数たちはマウントやアンマウント，アップデートが完全に終わったタイミングで実行したいわけです．\n\nそこで，スケジューラの方で`post`という種類のキューを新たに実装します．これは既存の queue の flush が終わった後に flush されるようなものです．  \nイメージ ↓\n\n```ts\nconst queue: SchedulerJob[] = [] // 既存実装\nconst pendingPostFlushCbs: SchedulerJob[] = [] // 今回新しく作る queue\n\nfunction queueFlush() {\n  queue.forEach(job => job())\n  flushPostFlushCbs() // queue の flush の後で flush する\n}\n```\n\nまた，これに伴って，pendingPostFlushCbs に enqueue するような API も実装しましょう．\nそして，それを使って renderer で作用を pendingPostFlushCbs に enqueue しましょう．\n\n今回対応するライフサイクル\n\n- onMounted\n- onUpdated\n- onUnmounted\n- onBeforeMount\n- onBeforeUpdate\n- onBeforeUnmount\n\n以下のようなコードが動くことを目指して実装してみましょう！\n\n```ts\nimport {\n  createApp,\n  h,\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  ref,\n} from 'chibivue'\n\nconst Child = {\n  setup() {\n    const count = ref(0)\n    onBeforeMount(() => {\n      console.log('onBeforeMount')\n    })\n\n    onUnmounted(() => {\n      console.log('onUnmounted')\n    })\n\n    onBeforeUnmount(() => {\n      console.log('onBeforeUnmount')\n    })\n\n    onBeforeUpdate(() => {\n      console.log('onBeforeUpdate')\n    })\n\n    onUpdated(() => {\n      console.log('onUpdated')\n    })\n\n    onMounted(() => {\n      console.log('onMounted')\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const mountFlag = ref(true)\n\n    return () =>\n      h('div', {}, [\n        h('button', { onClick: () => (mountFlag.value = !mountFlag.value) }, [\n          'toggle',\n        ]),\n        mountFlag.value ? h(Child, {}, []) : h('p', {}, ['unmounted']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/010_lifecycle_hooks)\n"
  },
  {
    "path": "book/online-book/src/ja/40-basic-component-system/020-provide-inject.md",
    "content": "# Provide/Inject の実装\n\n## Provide/Inject を実装しよう\n\nProvide と Inject の実装です．こちらも実装は至ってシンプルです．  \n基本的なコンセプトとしては， ComponentInternalInstance に provides (provide されたデータを格納する場所)と，親コンポーネントのインスタンスを保持しておいて，データを受け継ぐだけです．\n\n一点，注意する点としては，provide のエントリポイントは 2 種類あるということで，一つはコンポーネントの setup 時という想像のつきやすいものですが，  \nもう一つは App の provide を呼ぶケースです．\n\n```ts\nconst app = createApp({\n  setup() {\n    //.\n    //.\n    //.\n    provide('key', someValue) // これはコンポーネントから provide するケース\n    //.\n    //.\n  },\n})\n\napp.provide('key2', someValue2) // App に provide\n```\n\nさて，app で provide したものはどこに保持しておきましょう ? app はコンポーネントではないですからね．困りました．\n\n答えを言ってしまうと，app のインスタンスに AppContext というオブジェクトを持つことにして，provides というオブジェクトをこの中で保持するようにします．\n\nこの，AppContext には将来的にグローバルコンポーネントやカスタムディレクティブの設定を持たせます．\n\nさて，ここまでで説明するべきことは揃ったので，概ね以下のようなコードが動くように実装してみましょう！\n\n※ 想定しているシグネチャ\n\n```ts\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n)\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T\n```\n\n```ts\nconst Child = {\n  setup() {\n    const rootState = inject<{ count: number }>('RootState')\n    const logger = inject(LoggerKey)\n\n    const action = () => {\n      rootState && rootState.count++\n      logger?.('Hello from Child.')\n    }\n\n    return () => h('button', { onClick: action }, ['action'])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 1 })\n    provide('RootState', state)\n\n    return () =>\n      h('div', {}, [h('p', {}, [`${state.count}`]), h(Child, {}, [])])\n  },\n})\n\ntype Logger = (...args: any) => void\nconst LoggerKey = Symbol() as InjectionKey<Logger>\n\napp.provide(LoggerKey, window.console.log)\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/020_provide_inject)\n"
  },
  {
    "path": "book/online-book/src/ja/40-basic-component-system/030-component-proxy-setup-context.md",
    "content": "# コンポーネントの Proxy と setupContext\n\n## コンポーネントの Proxy\n\nコンポーネントが持つ重要な概念として Proxy というものがあります．  \nこれは，簡単にいうと，コンポーネントのインスタンスが持つデータ(public なプロパティ)にアクセスするための Proxy で，\nこの Proxy に setup の結果(ステートや関数)，data，props などのアクセスはまとめてしまいます．\n\n以下のようなコードを考えてみましょう．(chibivue で実装していない範囲のものも含みます．普段の Vue だと思ってください)\n\n```vue\n<script>\nexport default defineComponent({\n  props: { parentCount: { type: Number, default: 0 } },\n  data() {\n    return { dataState: { count: 0 } }\n  },\n  methods: {\n    incrementData() {\n      this.dataState.count++\n    },\n  },\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return { state, increment }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <p>count (parent): {{ parentCount }}</p>\n\n    <br />\n\n    <p>count (data): {{ dataState.count }}</p>\n    <button @click=\"incrementData\">increment (data)</button>\n\n    <br />\n\n    <p>count: {{ state.count }}</p>\n    <button @click=\"increment\">increment</button>\n  </div>\n</template>\n```\n\nこのコードは正常に動作するわけですが，さて template へはどうやってバインドしているのでしょうか ?\n\nもう一つ例を挙げます．\n\n```vue\n<script setup>\nconst ChildRef = ref()\n\n// コンポーネントが持つメソッドやデータにアクセスできる\n// ChildRef.value?.incrementData\n// ChildRef.value?.increment\n</script>\n\n<template>\n  <!-- Childは先ほどのコンポーネント -->\n  <Child :ref=\"ChildRef\" />\n</template>\n```\n\nこちらも，ref を介してコンポーネントの情報にアクセスすることができます．\n\nこれをどうやって実現しているかというと，ComponentInternalInstance に proxy というプロパティをもち，ここにはデータアクセスのための Proxy を持っています．\n\nつまり，template (render 関数)や ref は instance.proxy を参照しているということです．\n\n```ts\ninterface ComponentInternalInstance {\n  proxy: ComponentPublicInstance | null\n}\n```\n\nこの proxy の実装はもちろん Proxy で実装されていて，概ね，以下のようなイメージです．\n\n```ts\ninstance.proxy = instance.proxy = new Proxy(\n  instance,\n  PublicInstanceProxyHandlers,\n)\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get(instance: ComponentRenderContext, key: string) {\n    const { setupState, ctx, props } = instance\n\n    // key を元に setupState -> props -> ctx の順にチェックして存在していれば値を返す\n  },\n}\n```\n\n実際にこの Proxy を実装してみましょう！\n\n実装できたら render 関数や ref にはこの proxy を渡すように書き換えてみましょう．\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/030_component_proxy)\n\n※ ついでに defineComponent の実装とそれに関連する型付も実装しています． (そうすると proxy のデータの型を推論できるようになります．)\n\n![Component type inference result](/figures/40-basic-component-system/component-proxy-setup-context/infer-component-types.png)\n\n## setupContext\n\nhttps://ja.vuejs.org/api/composition-api-setup.html#setup-context\n\nVue には setupContext という概念があります．これは setup 内に公開される context で，emit や expose などが挙げられます．\n\n現時点では emit は使えるようにはなっているものの，少々雑に実装してしまっています．\n\n```ts\nconst setupResult = component.setup(instance.props, {\n  emit: instance.emit,\n})\n```\n\nSetupContext というインタフェースをきちんと定義して，インスタンスが持つオブジェクトとして表現しましょう．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupContext: SetupContext | null // 追加\n}\n\nexport type SetupContext = {\n  emit: (e: string, ...args: any[]) => void\n}\n```\n\nそして，インスタンスを生成する際に setupContext を生成し，setup 関数を実行する際の第二引数にこのオブジェクトを渡すようにしましょう．\n\n## expose\n\nここまでできたら emit 以外の SetupContext も実装してみます．  \n今回は例として，expose を実装してみます．\n\nexpose は，パブリックなプロパティを明示できる関数です．  \n以下のような開発者インタフェースを目指しましょう．\n\n```ts\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const log = () => {\n      console.log(\n        child.value.count,\n        child.value.count2, // cannot access\n        child2.value.count,\n        child2.value.count2,\n      )\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: log }, ['log']),\n      ])\n  },\n})\n```\n\nexpose を使用しないコンポーネントでは今まで通り，デフォルトで全てが public です．\n\n方向性としては，インスタンス内に `exposed` というオブジェクトを持つことにし，ここに値が設定されていれば templateRef に関してはこのオブジェクトを ref に渡す感じです．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  exposed: Record<string, any> | null // 追加\n}\n```\n\nそしてここにオブジェクトを登録できるように expose 関数を実装していきましょう．\n\n## ProxyRefs\n\nこのチャプターで proxy や exposedProxy を実装してきましたが，実は少々本家の Vue とは違う部分があります．  \nそれは，「ref は unwrap される」という点です．(proxy の場合は proxy というより setupState がこの性質を持っています．)\n\nこれらは ProxyRefs というプロキシで実装されていて，handler は`shallowUnwrapHandlers`という名前で実装されています．  \nこれにより，template を記述する際や proxy を扱う際に ref 特有の value の冗長さを排除できるようになっています．\n\n```ts\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key]\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value\n      return true\n    } else {\n      return Reflect.set(target, key, value, receiver)\n    }\n  },\n}\n```\n\n```vue\n<template>\n  <!-- <p>{{ count.value }}</p>  このように書く必要はない -->\n  <p>{{ count }}</p>\n</template>\n```\n\nここまで実装すると以下のようなコードが動くようになるはずです．\n\n```ts\nimport { createApp, defineComponent, h, ref } from 'chibivue'\n\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>child {{ count }} {{ count2 }}</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>child2 {{ count }} {{ count2 }}</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const increment = () => {\n      child.value.count++\n      child.value.count2++ // cannot access\n      child2.value.count++\n      child2.value.count2++\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n## Template へのバインディングと with 文\n\n実は，このチャプターの変更により問題が発生しています．  \n以下のようなコードを動かしてみましょう．\n\n```ts\nconst Child2 = {\n  setup() {\n    const state = reactive({ count: 0 })\n    return { state }\n  },\n  template: `<p>child2 count: {{ state.count }}</p>`,\n}\n```\n\nなんの変哲もないコードですが，実はこれは動きません．  \nstate が定義されていないと怒られてしまいます．\n\n![state is not defined runtime error](/figures/40-basic-component-system/component-proxy-setup-context/state-is-not-defined.png)\n\nこれがなぜかというと，with 文の引数として Proxy を渡す場合，has を定義しないといけないためです．\n\n[Creating dynamic namespaces using the with statement and a proxy (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with#creating_dynamic_namespaces_using_the_with_statement_and_a_proxy)\n\nというわけで，PublicInstanceProxyHandlers に has を実装してみましょう．  \nsetupState, props, ctx のいずれかに key が存在していれば true を返すようにします．\n\n```ts\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  // .\n  // .\n  // .\n  has(\n    { _: { setupState, ctx, propsOptions } }: ComponentRenderContext,\n    key: string,\n  ) {\n    let normalizedProps\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    )\n  },\n}\n```\n\nこれで正常に動くようになれば OK です！\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/040_setup_context)\n"
  },
  {
    "path": "book/online-book/src/ja/40-basic-component-system/040-component-slot.md",
    "content": "# スロット\n\n## デフォルトスロットの実装\n\nVue にはスロットと呼ばれる機能があります．そしてこのスロットには，default slot, named slot, scoped slot の 3 種類があります．  \nhttps://vuejs.org/guide/components/slots.html#slots\n\n今回はこれらのうち，デフォルトスロットを実装していきます．\n目指す開発者インタフェースは以下のようなものです．\n\nhttps://vuejs.org/guide/extras/render-function.html#passing-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () => h('div', {}, [slots.default()])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () => h(MyComponent, {}, () => 'hello')\n  },\n})\n```\n\n仕組みは単純で，スロットの定義側では，setupContext として slots を受け取れるようにしておき，使用する側で h 関数でコンポーネントをレンダリングする際に，children としてレンダー関数を渡すだけです．\nおそらく，皆さんが最も馴染み深い使い方は，SFC で template に slot 要素を置いたりする使い方だとは思うのですが，そちらは別途 template のコンパイラを実装する必要があるので，今回は省略します．(Basic Template Compiler 部門で扱います．)\n\n例の如く，インスタンスに slots を保持しておけるプロパティを追加し，createSetupContext で SetupContext として混ぜます．  \nh 関数も第 3 引数として配列だけではなく，レンダー関数を受け取れるような形にして，レンダー関数が来た場合にはコンポーネントのインスタンスを生成するタイミングでインスタンスの default slot として設定してあげます．  \nとりあえずここまで実装してみましょう！\n\n(children の normalize 実装に伴って ShapeFlags を少々変更しています．)\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/050_component_slot)\n\n## 名前付きスロット/スコープ付きスロットの実装\n\nデフォルトスロットの拡張です．  \n今度は関数ではなく，オブジェクトで渡せるようにしてみましょう．\n\nスコープ付きスロットに関しては，レンダー関数の引数を定義してあげるだけです．  \nこれをみても分かる通り，レンダー関数を使用する際はスコープ付きスロットという区別をわざわざする必要もないように感じます．  \nそれはその通りで，スロットの正体はただのコールバック関数で，それに引数を持たせるためにスコープ付きスロットという名前で API を提供しています．  \nもちろん，これから Basic Template Compiler の部門でスコープ付きスロットを扱えるようなコンパイラを実装しますが，それはこれらの形に変換されているのです．\n\nhttps://vuejs.org/guide/components/slots.html#scoped-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () =>\n      h('div', {}, [\n        slots.default?.(),\n        h('br', {}, []),\n        slots.myNamedSlot?.(),\n        h('br', {}, []),\n        slots.myScopedSlot2?.({ message: 'hello!' }),\n      ])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h(\n        MyComponent,\n        {},\n        {\n          default: () => 'hello',\n          myNamedSlot: () => 'hello2',\n          myScopedSlot2: (scope: { message: string }) =>\n            `message: ${scope.message}`,\n        },\n      )\n  },\n})\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/060_slot_extend)\n"
  },
  {
    "path": "book/online-book/src/ja/40-basic-component-system/050-options-api.md",
    "content": "# Options APIに対応する\n\n## Options API\n\nここまででかなりのことを Composition API で実装することができるようになりましたが，Options API も対応してみましょう．\n\n本書では以下を対応しています．\n\n- props\n- data\n- computed\n- method\n- watch\n- slot\n- lifecycle\n  - onMounted\n  - onUpdated\n  - onUnmounted\n  - onBeforeMount\n  - onBeforeUpdate\n  - onBeforeUnmount\n- provide/inject\n- $el\n- $data\n- $props\n- $slots\n- $parent\n- $emit\n- $forceUpdate\n- $nextTick\n\n実装方針としては，componentOptions.ts に applyOptions という関数を用意し，setupComponent の最後の方で実行します．\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n  // ↑ ここまでは既存実装\n\n  setCurrentInstance(instance)\n  applyOptions(instance)\n  unsetCurrentInstance()\n}\n```\n\nOptions API では this を頻繁に扱うような開発者インタフェースになっています．\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { message: 'hello' }\n  },\n\n  methods: {\n    greet() {\n      console.log(this.message) // こういうやつ\n    },\n  },\n})\n```\n\nこの this は内部的にはコンポーネントの proxy を指すようになっていて，オプションを apply する際にこの proxy を bind しています．\n\n実装イメージ ↓\n\n```ts\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance\n  const publicThis = instance.proxy! as any\n  const ctx = instance.ctx\n\n  const { methods } = options\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key]\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis)\n      }\n    }\n  }\n}\n```\n\n基本的にはこの原理を使って一つずつ実装していけば難しくないはずです．\n\ndata をリアクティブにしたければ reactive 関数をここで呼び出しますし，computed したければ computed 関数をここで呼び出します． (provide/inject も同様)\n\napplyOptions が実行される前には setCurrentInstance によってインスタンスがセットされているので，いつもと同じようにこれまで作ってきた api(CompositionAPI)を呼んであげれば OK です．\n\n`$`から始まるプロパティについては componentPublicInstance の方の実装で，PublicInstanceProxyHandlers の getter で制御しています．\n\n## Options API の型付\n\n機能的には上記のように実装していけばいいのですが，Options API は型付が少々複雑です．\n\n一応，本書では OptionsAPI に関しても基本的な型付はサポートしています．\n\n難しいポイントとしては，各オプションのユーザーの定義によって this の型が変動する点です．data オプションで number 型の count というプロパティを定義した場合には computed や method での this には `count: number` が推論されたいわけです．\n\nもちろん，data だけではなく computed や methods に定義されたものについても同様です．\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { count: 0 }\n  },\n\n  methods: {\n    myMethod() {\n      this.count // number\n      this.myComputed // number\n    },\n  },\n\n  computed: {\n    myComputed() {\n      return this.count // number\n    },\n  },\n})\n```\n\nこれを実現するには少々複雑な型パズルを実装する必要があります．(たくさんジェネリクスでバケツリレーします．)\n\ndefineComponent に対する型付を起点に，ComponentOptions, ComponentPublicInstance にリレーするためにいくつかの型を実装します．\n\nここでは一旦，data オプションと methods に絞って説明します．\n\nまずはいつもの ComponentOptions という型です．\nこちらもジェネリックに拡張し，data と methods の型を受け取れるように D と M というパラメータを取るようにします．\n\n```ts\nexport type ComponentOptions<\n  D = {},\n  M extends MethodOptions = MethodOptions\n> = {\n  data?: () => D;,\n  methods?: M;\n};\n\ninterface MethodOptions {\n  [key: string]: Function;\n}\n```\n\nここまでは特に難しくないかと思います．これが defineComponent の引数に当てられる型です．  \nもちろん，defineComponent の方でも D と M を受け取れるようにします．これによってユーザーが定義した型をリレーしていけるようになります．\n\n```ts\nexport function defineComponent<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n>(options: ComponentOptions<D, M>) {}\n```\n\n問題は method で扱う this に対して D をどうやって mix するか(どうやって this.count のような推論を可能にするか)です．\n\nまず，手始めに D や M は ComponentPublicInstance にマージされる(proxy にマージされる)ので以下のようになることがわかるかと思います．(ジェネリックに拡張します．)\n\n```ts\ntype ComponentPublicInstance<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n> = {\n  /** public instance が持ついろんな型 */\n} & D &\n  M\n```\n\nここまでできたら，ComponentOptions の this にインスタンスの型を混ぜ込みます．\n\n```ts\ntype ComponentOptions<D = {}, M extends MethodOptions = MethodOptions> = {\n  data?: () => D\n  methods?: M\n} & ThisType<ComponentPublicInstance<D, M>>\n```\n\nこうしておくことで，option 中の this から data や method に定義したプロパティを推論することができます．\n\n実際には props であったり，computed, inject など様々な型を推論する必要がありますが，基本原理はこれと同じです．  \nぱっと見ジェネリクスがたくさんあったり，型の変換(inject から key だけを取り出したり)が混ざっているのでウッとなってしまうかもしれませんが落ち着いて原理に戻って実装すれば大丈夫なはずです．  \n本書のコードでは本家の Vue をインスパイアして，`CreateComponentPublicInstance`という抽象化を一段階挟んでいたり，`ComponentPublicInstanceConstructor`と言う型を実装していますが，あまり気にしないでください．(興味があればそこも読んでみてください！　)\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/070_options_api)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/010-transform.md",
    "content": "# Transformer の実装 と Codegen のリファクタ(Basic Template Compiler 部門スタート)\n\n## 既存実装のおさらい\n\nさて，ここからはテンプレートのコンパイラをより本格的に実装していきます．  \nMinimum Example 部門でやったところから少し時間が空いてしまったので，今の実装がどうなっていたか少しおさらいをしておきましょう．  \n主なキーワードは Parse, AST, Codegen でした．\n\n![Minimum compiler pipeline](/figures/50-basic-template-compiler/transform/basic-compiler-pipeline.svg)\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  const code = generate(ast, option)\n  return code\n}\n```\n\n実は，この構成は本家のものと少し違っています．  \n少し本家のコードを覗いてみましょう．\n\nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/compile.ts#L61\n\nお分かりいただけるでしょうか....\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  transform(ast)\n  const code = generate(ast, option)\n  return code\n}\n```\n\nのようになっていることが．\n\n今回はこの transform という関数を実装していきます．\n\n![Compiler pipeline with transformer](/figures/50-basic-template-compiler/transform/compiler-pipeline-with-transformer.svg)\n\n## transform とは?\n\n上記のコードでもなんとなく想像がつく通り，パースによって得られた AST を transform によってなんらかしらの形に変換しています．\n\nここを読んでれば，なんとなく想像がつくかもしれません．  \nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/ast.ts#L43C1-L51C23\n\nこの，VNODE_CALL と JS から始まる名の付いた AST コードこそが今回扱うものです．\nVue.js のテンプレートコンパイラは，Template を解析した結果としての AST と，生成するコードを表す AST で分かれています．  \n今の我々の実装は前者の AST のみを扱っています．\n\n`<p>hello</p>`というテンプレートが入力として与えられたことを考えてみます．\n\nまず，パースによって以下のような AST が生成されます．ここまでは既存実装の通りです．\n\n```ts\ninterface ElementNode {\n  tag: string\n  props: object /** 省略 */\n  children: (ElementNode | TextNode | InterpolationNode)[]\n}\n\ninterface TextNode {\n  content: string\n}\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {},\n  \"children\": [{ \"content\": \"hello\" }]\n}\n```\n\n「生成するコードを表す AST」というのがどのようなものかというと，まずは生成するコードがどのようなものかについて考えてみてください．\n以下のようなものだと思います．\n\n```ts\nh('p', {}, ['hello'])\n```\n\nこれを表す AST だということです．つまり，生成されるべき JavaScript を表現するための AST で，概ね以下のようなオブジェクトです．\n\n```ts\ninterface VNodeCall {\n  tag: string\n  props: PropsExpression\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode // single text child\n    | undefined\n}\n\ntype PropsExpression = ObjectExpression | CallExpression | ExpressionNode\ntype TemplateChildNode = ElementNode | InterpolationNode | TextNode\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {\n    \"type\": \"ObjectExpression\",\n    \"properties\": []\n  },\n  \"children\": { \"content\": \"hello\" }\n}\n```\n\nこのように，Codegen で生成されるコードを AST として表現したものが「生成するコードを表す AST」です．\n今はこれをわざわざ分けるほどの利点が感じられないかもしれませんが，これからディレクティブを実装したりしていくにあたっては便利なのです．\ninput に着目した AST と output に着目した AST に分ける感じで，`input の AST -> output の AST` の変換を行う関数こそが `transform` です．\n\n## Codegen Node\n\n流れは掴めたと思うので，改めてどのような Node を扱うのか(どのような Node に変換したいのか)を確認してみます．\n\n最終的には以下の Node を扱います．\nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/ast.ts#L43C1-L51C23\n\nこの，\"JS\" から始まる Node + VNODE_CALL が output に着目した AST (以下 CodegenNode と呼びます) です．\nしかし，CodegenNode の全てがこれらの Node で構成されているというわけではなく，ElementNode や InterpolationNode などを含んで構成されることになります．\n\n今回扱うものを列挙しつつコメントで説明します．多少省略しているものもあるので，正確にはソースコードを参照してください．\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[]\n}\n\n// h関数をcallしている式を表すNodeです。\n// `h(\"p\", { class: 'message'}, [\"hello\"])` のようなものを想定しています。\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: ObjectExpression | undefined // NOTE: ソースコードでは PropsExpression として実装しています (今後拡張があるので)\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode\n\n// JavaScript の Object を想定しているNodeです。 VNodeCall の props などが持つことになります。\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION\n  properties: Array<Property>\n}\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY\n  key: ExpressionNode\n  value: JSChildNode\n}\n\n// JavaScript の Array を想定しているNodeです。 VNodeCall の children などが持つことになります。\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION\n  elements: Array<string | Node>\n}\n```\n\n## Transformer の設計\n\ntransformer の実装をしていく前に設計についてです．\nまず，初めに押さえておくべきことは transformer は 2 種類あるということで，NodeTransform と DirectiveTransform というものが存在します．\nこれらは名前の通り，Node の変換とディレクティブの変換に関するもので，以下のようなインタフェースを取ります．\n\n```ts\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[]\n\n// TODO:\n// export type DirectiveTransform = (\n//   dir: DirectiveNode,\n//   node: ElementNode,\n//   context: TransformContext,\n// ) => DirectiveTransformResult;\nexport type DirectiveTransform = Function\n```\n\nDirectiveTransform の方はのちのチャプターのディレクティブを実装していくところで取り上げるのでとりあえず Function というふうにしておきます．  \nNodeTransform も DirectiveTransform も実態としては関数です．AST を変換するための関数だと思ってもらえれば問題ないです．  \nNodeTransform の結果が関数になっていることに注目してください．transform を実装する際に関数を return するように実装しておくと，その関数はその node の transform 後に実行されるようになっています．(onExit という名前のプロセスです．)  \nNode の transform が適応された後に実行したい処理などはここに記述します．これについては後述の traverseNode という関数の説明と一緒に説明を行います．\nインタフェースの説明は主には上記の通りです．\n\nそして，より具体的な実装として，Element を変換するための transformElement であったり，式を変換するための transformExpression などがあります．\nDirectiveTransform の実装としては各ディレクティブの実装が存在しています．\nこれらの実装は compiler-core/src/transforms に実装されています．具体的なそれぞれの変換処理はここに実装されます．\n\nhttps://github.com/vuejs/core/tree/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/transforms\n\nイメージ ↓\n\n![Transform type relationships](/figures/50-basic-template-compiler/transform/transform-type-relationships.svg)\n\n次に context についてですが，TransformContext にはこれらの transform の際に扱う情報や関数を持ちます．  \n今後また追加されていきますが，初めはこれだけで Ok です．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n}\n```\n\n## Transformer の実装\n\nそれでは，実際に transform 関数を見ていきます．まずはそれぞれの変換処理の内容に寄らない大枠の説明からです．\n\n構成は非常にシンプルで，context を生成して traverseNode するだけです．\nこの traverseNode が変換の実装本体です．\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n}\n```\n\ntraverseNode では，基本的には context に保存してある nodeTransforms (Node を変換するための関数を集めたもの)を node に適応するだけです．  \n子 Node を持つものに関しては子 Node も traverseNode を通してあげます．  \nインタフェースの説明時に登場した onExit もここに実装があります．\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  context.currentNode = node\n\n  const { nodeTransforms } = context\n  const exitFns = [] // transform後に行いたい処理\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context)\n\n    // transform後に行いたい処理を登録しておく\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit)\n      } else {\n        exitFns.push(onExit)\n      }\n    }\n    if (!context.currentNode) {\n      return\n    } else {\n      node = context.currentNode\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n\n  context.currentNode = node\n\n  // transform後に行いたい処理を実行\n  let i = exitFns.length\n  while (i--) {\n    exitFns[i]() // transformが終わったことを前提にした処理を実行することができる\n  }\n}\n\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    traverseNode(child, context)\n  }\n}\n```\n\n続いて具体的な変換処理についてですが，今回は例として transformElement を実装してみます．\n\ntransformElement では主に NodeTypes.ELEMENT の node を VNodeCall に変換していきます．\n\n```ts\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n  isSelfClosing: boolean\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\n// ↓↓↓↓↓↓ 変換 ↓↓↓↓↓↓ //\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n```\n\nただのオブジェクト to オブジェクトの変換ですので，それほど難しいものではないと思います．実際にソースコードを読んだりして実装してみましょう．  \n一応今回想定するコードは以下に貼っておきます．(ディレクティブの対応は別のチャプターで行います．)\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    const { tag, props } = node\n\n    const vnodeTag = `\"${tag}\"`\n    let vnodeProps: VNodeCall['props']\n    let vnodeChildren: VNodeCall['children']\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node)\n      vnodeProps = propsBuildResult.props\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0]\n        const type = child.type\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode\n        } else {\n          vnodeChildren = node.children\n        }\n      } else {\n        vnodeChildren = node.children\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren)\n  }\n}\n\nexport function buildProps(node: ElementNode): {\n  props: PropsExpression | undefined\n  directives: DirectiveNode[]\n} {\n  const { props } = node\n  let properties: ObjectExpression['properties'] = []\n  const runtimeDirectives: DirectiveNode[] = []\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i]\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop\n\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : '', true),\n        ),\n      )\n    } else {\n      // directives\n      // TODO:\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined\n  if (properties.length) {\n    propsExpression = createObjectExpression(properties)\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  }\n}\n```\n\n## Transform した AST をもとに Codegen する\n\nAST を Codegen 用に Transform したわけですから，Codegen の方ももちろん対応する必要があります．\nCodegen に入ってくる AST としては主に VNodeClass (とそれらが持つ Node)を想定したコードを書けば OK です．\n最終的にどのような文字列として generate したいかは今までと変わりありません．\n\n既存の Codegen は非常に簡素な実装になっているので，ここでもう少し形式的にしておきましょう．(結構ハードコードになっているので)  \nCodegen の方でも Codegen 用の context を持つことにして，生成したコードをそこに push していくような構成にしてみようと思います．  \nついでに，context の方に幾つかのヘルパー関数を実装してみます． (インデント系とか)\n\n```ts\nexport interface CodegenContext {\n  source: string\n  code: string\n  indentLevel: number\n  line: 1\n  column: 1\n  offset: 0\n  push(code: string, node?: CodegenNode): void\n  indent(): void\n  deindent(withoutNewLine?: boolean): void\n  newline(): void\n}\n```\n\n実装内容についてはここでは割愛しますが，それぞれの役割ごとに関数を分けただけで，実装方針の大きな変更はありません．\nディレクティブについてはまだ対応できていないため，その辺りの仮実装を消した兼ね合いで動いていない部分もありますが，\n概ね以下のようなコードが動いていれば OK です！\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> Hello World! </p>\n      <p> Count: {{ count }} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nここまでのソースコード:  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/010_transformer)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/020-v-bind.md",
    "content": "# ディレクティブを実装しよう (v-bind)\n\n## 方針\n\nここからは Vue.js の醍醐味であるディレクティブを実装していきます．  \n例の如く，ディレクティブも transformer に噛ませるのですが，そこで登場するのが DirectiveTransform というインタフェースです．\nDirectiveTransform は DirectiveNode,ElementNode を受け取り，Transform 後の Property を返すようなものになっています．\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult\n\nexport interface DirectiveTransformResult {\n  props: Property[]\n}\n```\n\nまずは今回目指す開発者インタフェースから確認してみましょう．\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const bind = { id: 'some-id', class: 'some-class', style: 'color: red' }\n    return { count: 1, bind }\n  },\n\n  template: `<div>\n  <p v-bind:id=\"count\"> v-bind:id=\"count\" </p>\n  <p :id=\"count * 2\"> :id=\"count * 2\" </p>\n\n  <p v-bind:[\"style\"]=\"bind.style\"> v-bind:[\"style\"]=\"bind.style\" </p>\n  <p :[\"style\"]=\"bind.style\"> :[\"style\"]=\"bind.style\" </p>\n\n  <p v-bind=\"bind\"> v-bind=\"bind\" </p>\n\n  <p :style=\"{ 'font-weight': 'bold' }\"> :style=\"{ font-weight: 'bold' }\" </p>\n  <p :style=\"'font-weight: bold;'\"> :style=\"'font-weight: bold;'\" </p>\n\n  <p :class=\"'my-class my-class2'\"> :class=\"'my-class my-class2'\" </p>\n  <p :class=\"['my-class']\"> :class=\"['my-class']\" </p>\n  <p :class=\"{ 'my-class': true }\"> :class=\"{ 'my-class': true }\" </p>\n  <p :class=\"{ 'my-class': false }\"> :class=\"{ 'my-class': false }\" </p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nv-bind には 概ね上記のような記法があります．詳しくは下記の公式ドキュメントを参照してください．  \nclass や style についても今回取り扱います．\n\nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\n## AST の変更\n\nまず，AST についてですが，今は exp, arg 共に string という簡易的なものになってしまっているので，ExpressionNode を受け取れるように変更します．\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined // ここ\n  arg: ExpressionNode | undefined // ここ\n}\n```\n\n改めて `name` と `arg` と `exp` について説明しておくと，\nname は v-bind や v-on などのディレクティブ名です．on や bind が入ります．\n今回は v-bind を実装していくので，bind が入ります．\n\narg は `:` で指定する引数です．v-bind でいうと， id や style などが入ります．  \n(v-on の場合は click や input などがここに入ってきます．)\n\nexp は右辺です．`v-bind:id=\"count\"` でいうと count が入ります．  \nexp も arg も，動的に変数を埋め込むことができるので，型は `ExpressionNode` になります．  \n( `v-bind:[key]=\"count\"` のように arg も動的にできるので)\n\n![DirectiveNode shape for v-bind](/figures/50-basic-template-compiler/v-bind/directive-node-shape.svg)\n\n## Parser の変更\n\nparser の実装をこの AST の変更に追従します．exp, arg を `SimpleExpressionNode` としてパースします．\n\nついでに v-on などで使う `@` やスロットで使う `#` などもパースします．  \n(正規表現を考えるのが面倒くさい(説明しながら徐々に追加するのが面倒臭い)のでとりあえず本家のものをそのまま拝借します)  \n参考: https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/parse.ts#L802\n\n少し長いので，コード中にコメントを書きながら説明していきます．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  // .\n  // directive\n  const loc = getSelection(context, start)\n  // ここの正規表現は本家から拝借\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match =\n      // ここの正規表現は本家から拝借\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name,\n      )!\n\n    // name 部分のマッチを見て、`:` で始まっていた場合には bind として扱う\n    let dirName =\n      match[1] ||\n      (startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : '')\n\n    let arg: ExpressionNode | undefined\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2])\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      )\n\n      let content = match[2]\n      let isStatic = true\n\n      // `[arg]` のような動的な引数の場合、`isStatic` を false として、中身を content として取り出す\n      if (content.startsWith('[')) {\n        isStatic = false\n        if (!content.endsWith(']')) {\n          console.error(`Invalid dynamic argument expression: ${content}`)\n          content = content.slice(1)\n        } else {\n          content = content.slice(1, content.length - 1)\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      }\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    }\n  }\n}\n```\n\nこれで今回扱いたい AST Node にパースすることができました．\n\n## Transformer の実装\n\n続いて，この AST を Codegen 用の AST に transform する実装を書いていきます．  \n少々複雑なので，以下の図に軽く流れをまとめました．まずはそちらをご覧ください．  \n大まかに，必要な項目を挙げると，v-bind に引数が存在するかどうか，class かどうか，style かどうかです．  \n※ 今回関係してくる処理以外の部分は省略しています．(あまり厳格な図ではありませんがご了承ください．)\n\n![v-bind transform flow](/figures/50-basic-template-compiler/v-bind/transform-vbind-flow.svg)\n\nまず，前提として，ディレクティブというものは基本的に要素 (element) に対して宣言されているものなので，\n\nディレクティブに関する transformer は transformElement に呼ばれます．\n\n今回は v-bind を実装したいので，transformVBind と言う関数を実装していくのですが，  \n注意点として，この関数では args が存在している宣言のみの変換を行う点が挙げられます．\n\ntransformVBind は，\n\n```\nv-bind:id=\"count\"\n```\n\nのようなものを，\n\n```ts\n{\n  id: count\n}\n```\n\nというオブジェクト(実際にはこのオブジェクトを表す Codegen Node)に変換する役割のみを持ちます．\n\n本家の実装でも，以下のような説明がなされています．\n\n> codegen for the entire props object. This transform here is only for v-bind _with_ args.\n\n引用元: https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/transforms/vBind.ts#L13C1-L14C16\n\n流れを見てもわかる通り，transformElement では directive の arg をチェックして，存在していなければ transformVBind を実行せず mergeProps という関数呼び出しに変換しています．\n\n`v-bind=\"hoge\"`の形式で渡された引数と，そのほかの props をマージする関数です．\n\n```vue\n<p v-bind=\"bindingObject\" class=\"my-class\">hello</p>\n```\n\n↓\n\n```ts\nh('p', mergeProps(bindingObject, { class: 'my-class' }), 'hello')\n```\n\nまた，class と style に関してはさまざまな開発者インタフェースを持っているため，normalize する必要があります．  \nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\nnormalizeClass と normalizeStyle という関数を実装し，それぞれに適用します．\n\narg が動的な場合は，特定が不可能なため，normalizeProps という関数を実装し，それを呼び出すようにします． (内部で normalizeClass と normalizeStyle を呼び出します)\n\nさてここまで実装できたら動作を見てみましょう！\n\n![v-bind test result in the browser](/figures/50-basic-template-compiler/v-bind/vbind-test-result.png)\n\nとっても良さそうです！\n\n## Same-name Shorthand (Vue 3.4+)\n\nVue 3.4 から，属性名と変数名が同じ場合の省略記法がサポートされています．\n\n```vue\n<!-- 従来の記法 -->\n<div :id=\"id\" :class=\"class\" :style=\"style\"></div>\n\n<!-- Same-name Shorthand -->\n<div :id :class :style></div>\n```\n\nこの機能を実装するには，パーサーとトランスフォーマーを修正する必要があります．\n\n### パーサーでの対応\n\n`:prop` のように値が省略された場合，`exp` は undefined になります．\n\n```ts\nreturn {\n  type: NodeTypes.DIRECTIVE,\n  name: dirName,\n  exp: value && {  // value がない場合は exp は undefined\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    content: value.content,\n    isStatic: false,\n    loc: value.loc,\n  },\n  loc,\n  arg,\n};\n```\n\n### トランスフォーマーでの対応\n\n`transformBind` で `exp` が undefined の場合，`arg` の内容を `exp` として使用します．\n\n```ts\nexport const transformBind: DirectiveTransform = (dir, _node, context) => {\n  let { exp } = dir;\n  const arg = dir.arg!;\n\n  // Same-name shorthand: :prop は :prop=\"prop\" と同じ\n  if (!exp) {\n    if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {\n      // 動的な引数には対応しない\n      context.onError(\n        createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, dir.loc)\n      );\n      return { props: [] };\n    }\n    // arg の内容を exp として使用\n    const propName = camelize(arg.content);\n    exp = {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      content: propName,\n      isStatic: false,\n      loc: arg.loc,\n    };\n  }\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp)] };\n};\n```\n\nこれにより，`:id` と書くだけで `:id=\"id\"` と同じ意味になります．\n\n次回は v-on を実装していきます．\n\nここまでのソースコード:\n[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/020_v_bind)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/022-transform-expression.md",
    "content": "# transformExpression\n\n## 目指す開発者インターフェースと現状の課題\n\nまずはこちらのコンポーネントを見てください．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button :onClick=\"increment\">count + count is: {{ count + count }}</button>\n  </div>\n</template>\n```\n\nこのコンポーネントにはいくつかの問題があります．  \nこのコンポーネントは SFC で記述されているため，with 文が使用されません．\nつまり，バインディングがうまくいっていません．\n\nコンパイルされたコードを見てみましょう．\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: increment }), [\n      'count + count is: ',\n      _ctx.count + count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n- 上手くいっていないポイント 1  \n  イベントハンドラに登録される increment が \\_ctx を辿れていません．  \n  これは当たり前で，前回の v-bind の実装では prefix の付与を行なっていないためです．\n- 上手くいっていないポイント 2  \n  count + count が \\_ctx を辿れていません．  \n  マスタッシュに関しては，先頭に `_ctx.` を付与しているだけで，それ以外の識別子に対応できていません．  \n  このように，式の途中で登場する識別子は全て `_ctx.` を付与する必要があります．これはマスタッシュに限らず全ての箇所で同様です．\n\n式中に登場する識別子に対して `_ctx.` を付与して行くような処理が必要なようです．\n\n::: details 以下のようにコンパイルしたい\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: _ctx.increment }), [\n      'count + count is: ',\n      _ctx.count + _ctx.count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n:::\n\n::: warning\n\n実は，本家の実装では少しだけアプローチが違います．\n\n以下をみてもらえれば分かる通り，本家では setup 関数からバインディングされるものは `$setup` を介して解決されます．\n\n![Original resolve bindings output](/figures/50-basic-template-compiler/transform-expression/resolve-bindings-original.png)\n\nしかしこの実装をするのは少々大変なので，簡略化として `_ctx.` を付与するように実装します．(props も setup も全て \\_ctx から解決する)\n\n:::\n\n## 実装方針\n\nやりたいことを一言で言うなら，「ExpressionNode 上のあらゆる Identifier(の name の先頭)に対して`_ctx.`を付与したい」です．\n\n少し噛み砕いて説明します．  \nおさらいになりますが，プログラムというのはパースされることによって AST として表現されます．  \nそして，プログラムを表す AST の Node には大きく分けて Expression と Statement の 2 種類がありました．\nいわゆる式と文です．\n\n```ts\n1 // これは Expression\nident // これは Expression\nfunc() // これは Expression\nident + func() // これは Expression\n\nlet a // これは Statement\nif (!a) a = 1 // これは Statement\nfor (let i = 0; i < 10; i++) a++ // これは Statement\n```\n\n今回考えたいのは Expression (式)です．  \nExpression にはさまざまな種類があります．Identifier というのはそのうちの一つで，識別子で表現された Expression です．  \n(概ね変数名だと思ってもらえれば問題ないです)\n\nExpressionNode 上のあらゆる Identifier というのは，\n\n```ts\n1 // なし\nident // ident --- (1)\nfunc() // func --- (2)\nident + func() // ident, func --- (3)\n```\n\nのようなもので，(1) に関してはそれ単体が Identifier であり，(2) は CallExpression の callee が Identifier，  \n(3) は BinaryExpression の left が Identifier，right が CallExpression でその callee が Identifier になっています．\n\nこのように，Identifier は式中のいろんなところで登場します．\n\nAST は以下のサイトでプログラムを入力すれば容易に観察できるので，ぜひさまざまな Expression 上の Identifier を観測してみてください．  \nhttps://astexplorer.net/#/gist/670a1bee71dbd50bec4e6cc176614ef8/9a9ff250b18ccd9000ed253b0b6970696607b774\n\n## Identifier を探索する\n\nやりたいことは分かったとして，どうやって実装していきましょうか．\n\nとても難しそうな感じがしますが，実は単純で，estree-walker というライブラリを使います．  \nhttps://github.com/Rich-Harris/estree-walker\n\nbabel で parse することによって得られた AST をこのライブラリを使って tree walk します．  \n使い方は非常に簡単で，walk 関数に AST を渡してあげて，第二引数に各 Node の処理を記述してあげれば良いです．  \nこの walk 関数は AST Node 単位で walk していくのですが，その Node に到達した時点の処理で処理を行うのが enter というオプションです．  \n他にも，その Node の去り際に処理をするための leave なども用意されています．今回はこの enter のみを扱います．\n\n`compiler-core/babelUtils.ts`を新たに作成して Identifier に対して操作を行えるような utility 関数を実装します．\n\nとりあえず estree-walker はインストールします．\n\n```sh\npnpm add estree-walker\n\npnpm add -D @babel/types # これも\n```\n\n```ts\nimport { Identifier, Node } from '@babel/types'\n\nimport { walk } from 'estree-walker'\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        onIdentifier(node)\n      }\n    },\n  })\n}\n```\n\nあとは式の AST を生成し，この関数に渡して node を書き換えながら transform を行なっていけばいいです．\n\n## transformExpression の実装\n\n### AST の変更とパーサ\n\n変換処理の本体である transformExpression を実装していきます．\n\nとりあえず，InterpolationNode は content として string ではなく SimpleExpressionNode を持つように変更します．\n\n```ts\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // [!code --]\n  content: ExpressionNode // [!code ++]\n}\n```\n\nそれに伴って parseInterpolation も修正です．\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  // .\n  // .\n  // .\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  }\n}\n```\n\n### transformer (本体)の実装\n\n式の変換に関しては他の transformer でも使えるようにしたいので，`processExpression` という関数として切り出します．  \ntransformExpression では，INTERPOLATION と DIRECTIVE が持つ ExpressionNode を処理します．\n\n```ts\nexport const transformExpression: NodeTransform = node => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp\n        const arg = dir.arg\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          dir.exp = processExpression(exp)\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg)\n        }\n      }\n    }\n  }\n}\n\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // TODO:\n}\n```\n\n続いて processExpression の実装の説明です．  \nまず，processExpression の内部に Identifier を書き換えるための rewriteIdentifier という関数を実装します．  \nnode が単体の Identifier だった場合はそのままこの関数を適用しておしまいです．\n\n一点注意があるのは，この processExpression は SFC の場合 (with 文を使わない場合)限定の話です．  \nつまりは isBrowser フラグが立っている場合にはそのまま node を返すように実装しておきます．  \nフラグは ctx 経由で受け取れるように実装を変更します．\n\nまた，true や false などのリテラルはそのままにしておきたいので，リテラルのホワイトリストを作成しておきます．\n\n```ts\nconst isLiteralWhitelisted = makeMap('true,false,null,this')\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    // ブラウザの場合には何もしない\n    return node\n  }\n\n  const rawExp = node.content\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`\n  }\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp)\n    if (!isLiteral) {\n      node.content = rewriteIdentifier(rawExp)\n    }\n    return node\n  }\n\n  // TODO:\n}\n```\n\nmakeMap とは vuejs/core で実装されている存在チェック用のヘルパー関数で，カンマ区切りで定義した文字列に一致しているかどうかを boolean で返してくれます．\n\n```ts\nexport function makeMap(\n  str: string,\n  expectsLowerCase?: boolean,\n): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null)\n  const list: Array<string> = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]\n}\n```\n\n問題はこの次で，SimpleExpressionNode (単純な Identifier ではない) をどのようにして node を transform していくかです．  \nこれからの話で少し注意して欲しい点が，Babel のパーサによって生成された JavaScript の AST と，我々が定義した chibivue の AST の２つを扱うことになるので，  \n混乱を避けるために前者のことを estree，後者のことを AST とこのチャプターでは呼ぶことにします．\n\n方針としては 2 段階に分けます．\n\n1. estree の node を置換しつつ，その node を収集していく\n2. 収集された node をもとに AST を構築する\n\nまずは 1 からやっていきます．  \nこちらは簡単で，元の SimpleExpressionNode の内容(文字列)を Babel で parse し，  \nestree を得ることができればあとはそれを先ほど作ったユーティリティ関数に通して rewriteIdentifier を噛ませます．  \nこの際に，ids という配列に収集していきます．\n\n```ts\nimport { parse } from '@babel/parser'\nimport { Identifier } from '@babel/types'\nimport { walkIdentifiers } from '../babelUtils'\n\ninterface PrefixMeta {\n  start: number\n  end: number\n}\n\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // .\n  // .\n  // .\n  const ast = parse(`(${rawExp})`).program // ※ この ast は estree のことです。\n  type QualifiedId = Identifier & PrefixMeta\n  const ids: QualifiedId[] = []\n\n  walkIdentifiers(ast, node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  })\n\n  // TODO:\n}\n```\n\n注意するべき点としては，ここまでではまだ estree を操作しただけで，ast の node は何も操作されていないという点です．\n\n### CompoundExpression\n\n続いて 2 です．ここで新しい AST Node を定義します．`CompoundExpressionNode`というものです．  \nCompound には「配合」「複合」といった意味が含まれます．  \nこの Node は children をもち，これらは少し特殊な値をとります．  \nまずは AST の定義をご覧ください．\n\n```ts\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n}\n```\n\nchildren は上記のような配列をとります．  \nこの Node の children が何を表しているのかは具体例を見た方がわかりやすいと思うので，具体例を挙げます．\n\n以下のような式が以下のような CompoundExpressionNode に解析されます．\n\n```ts\ncount * 2\n```\n\n```json\n{\n  \"type\": 7,\n  \"children\": [\n    {\n      \"type\": 4,\n      \"isStatic\": false,\n      \"content\": \"_ctx.count\"\n    },\n    \" * 2\"\n  ]\n}\n```\n\n結構ヘンテコな感じです．children が string の型をとっているのはこのような形になるためです．  \nCompoundExpression では Vue のコンパイラが必要な粒度で分割し，部分的に文字列で表現したり，部分的に Node で表現したりするものです．  \n具体的には今回のように Expression に存在する Identifier を書き換えたりする場合に Identifier の部分だけを  \n別の SimpleExpressionNode として分割したりする感じです．\n\nつまり，これからやることは，収集した estree の Identifier Node と source などをもとにこの CompoundExpression を生成することです．  \n以下のコードがその実装になります．\n\n```ts\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // .\n  // .\n  // .\n  const children: CompoundExpressionNode['children'] = []\n  ids.sort((a, b) => a.start - b.start)\n  ids.forEach((id, i) => {\n    const start = id.start - 1\n    const end = id.end - 1\n    const last = ids[i - 1]\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start)\n    if (leadingText.length) {\n      children.push(leadingText)\n    }\n\n    const source = rawExp.slice(start, end)\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    )\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end))\n    }\n  })\n\n  let ret\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc)\n  } else {\n    ret = node\n  }\n\n  return ret\n}\n```\n\nBabel によってパースされた Node は start と end (もと文字列のどこに当たるかのロケーション情報)を持っているのでそれをもとに rawExp から該当箇所を抜き出し，頑張って分割します．  \n詳しくはソースコードをじっくり眺めてみてください．ここまでの方針が理解できれば読めるはずです． (advancePositionWithClone などの実装も新規で行なっているのでその辺りも見てみてください．)\n\nCompoundExpressionNode を生成することができるようになったので，Codegen の方でも対応します．\n\n```ts\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNode(node.content, context, option)\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i]\n    if (isString(child)) {\n      // string の場合にはそのまま push\n      context.push(child)\n    } else {\n      // それ以外は Node を codegen する\n      genNode(child, context, option)\n    }\n  }\n}\n```\n\n(genInterpolation がただの genNode になってしまいましたがまぁ，一応残しておきます．)\n\n## 動かしてみる\n\nさて，ここまで実装できたらコンパイラを完成させて動かしてみましょう！\n\n```ts\n// transformExpressionを追加する\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformElement], { bind: transformBind }] // [!code --]\n  return [[transformExpression, transformElement], { bind: transformBind }] // [!code ++]\n}\n```\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(3)\n    const getMsg = (count: number) => `Count: ${count}`\n    return { count, getMsg }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> {{ 'Message is \"' + getMsg(count) + '\"'}} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/022_transform_expression)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/025-v-on.md",
    "content": "# v-on に対応する\n\n## リファクタリング\n\n実装を進めていく前に少しリファクタリングを行います．  \n現在，codegen で生成するコード中では，shared や runtime-core から export された helper 関数を多数 import (なり，分割代入で読み込んだり)しています．  \nそして，codegen(や transform)の実装の方ではその関数名をハードコードしてしまっています．これはあまりスマートではありません．\n\n今回は，これらを runtime-helper として，symbol で一元管理し，さらに，必要なものだけを読み込むような実装に変更してみようと思います．\n\nとりあえず，それぞれの helper を表す symbol を `compiler-core/runtimeHelpers.ts` に実装してみます．  \n今まで VNode の生成に関しては h 関数を使っていたのですが，これを機に本家の実装にならい createVNode を使うように変更しょう．  \nruntime-core/vnode から crateVNode を export して，genVNodeCall では createVNode を呼び出すコードに変更します．\n\n```ts\nexport const CREATE_VNODE = Symbol()\nexport const MERGE_PROPS = Symbol()\nexport const NORMALIZE_CLASS = Symbol()\nexport const NORMALIZE_STYLE = Symbol()\nexport const NORMALIZE_PROPS = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: 'createVNode',\n  [MERGE_PROPS]: 'mergeProps',\n  [NORMALIZE_CLASS]: 'normalizeClass',\n  [NORMALIZE_STYLE]: 'normalizeStyle',\n  [NORMALIZE_PROPS]: 'normalizeProps',\n}\n```\n\nCallExpression の callee として，symbol も使えるようにします．\n\n```ts\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION\n  callee: string | symbol\n}\n```\n\nTransformContext に，helper を登録する領域と登録するための関数を実装します．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n  helpers: Map<symbol, number> // これ\n  helper<T extends symbol>(name: T): T // これ\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n    helpers: new Map(),\n    helper(name) {\n      const count = context.helpers.get(name) || 0\n      context.helpers.set(name, count + 1)\n      return name\n    },\n  }\n\n  return context\n}\n```\n\nあとは，ハードコードしてしまっている部分をこの helper 関数に置き換えて，Preamble では登録された helper を使用するように書き換えます．\n\n```ts\n// 例)\npropsExpression = createCallExpression('mergeProps', mergeArgs, elementLoc)\n// ↓\npropsExpression = createCallExpression(\n  context.helper(MERGE_PROPS),\n  mergeArgs,\n  elementLoc,\n)\n```\n\ncreateVNodeCall にも context を渡すようにして，中で CREATE_VNODE を登録してあげます．\n\n```ts\nexport function createVNodeCall(\n  context: TransformContext | null, // これ\n  tag: VNodeCall['tag'],\n  props?: VNodeCall['props'],\n  children?: VNodeCall['children'],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  // ここ ------------------------\n  if (context) {\n    context.helper(CREATE_VNODE)\n  }\n  //  ------------------------\n\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  }\n}\n```\n\n```ts\nfunction genVNodeCall(\n  node: VNodeCall,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n  const { tag, props, children } = node\n\n  push(helper(CREATE_VNODE) + `(`, node) // createVNodeをcallするように\n  genNodeList(genNullableArgs([tag, props, children]), context, option)\n  push(`)`)\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  root.helpers = new Set([...context.helpers.keys()]) // root に helpersを持たせる\n}\n```\n\n```ts\n// 本家の実装に合わせてエイリアスとして `_` を prefix としてつける\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context\n\n  // astに登録された helper を元に helperの宣言を生成\n  const helpers = Array.from(ast.helpers)\n  push(\n    `const { ${helpers.map(aliasHelper).join(', ')} } = ${runtimeGlobalName}\\n`,\n  )\n  newline()\n}\n```\n\n```ts\n// genCallExpression で symbol をハンドリングして helperの呼び出しに変換します。\n\nexport interface CodegenContext {\n  // .\n  // .\n  // .\n  helper(key: symbol): string\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    // .\n    // .\n    // .\n    helper(key) {\n      return `_${helperNameMap[key]}`\n    },\n  }\n  // .\n  // .\n  // .\n  return context\n}\n\n// .\n// .\n// .\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n\n  // symbol の場合は helper から取得\n  const callee = isString(node.callee) ? node.callee : helper(node.callee)\n\n  push(callee + `(`, node)\n  genNodeList(node.arguments, context, option)\n  push(`)`)\n}\n```\n\nこれで今回行うリファクタは終わりです．ハードコードしていた部分綺麗にできました！\n\n::: details コンパイル結果\n\n※ 注意\n\n- input は前回の playground のものを使っています\n- 実際には `function` の前に `return` があります\n- 生成されたコードを prettier で整形しています\n\nこうやってみてみると余計な改行や空白があまり綺麗じゃありませんね...\n\nまぁ，これはまたどこかで改良しましょう．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      normalizeClass: _normalizeClass,\n    } = ChibiVue\n\n    return _createVNode('div', null, [\n      '\\n  ',\n      _createVNode('p', _normalizeProps({ id: count }), ' v-bind:id=\"count\" '),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ id: count * 2 }),\n        ' :id=\"count * 2\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' v-bind:[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' :[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode('p', _normalizeProps(bind), ' v-bind=\"bind\" '),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: { 'font-weight': 'bold' } }),\n        ' :style=\"{ font-weight: \\'bold\\' }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: 'font-weight: bold;' }),\n        ' :style=\"\\'font-weight: bold;\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass('my-class my-class2'),\n        }),\n        ' :class=\"\\'my-class my-class2\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ class: _normalizeClass(['my-class']) }),\n        ' :class=\"[\\'my-class\\']\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': true }),\n        }),\n        ' :class=\"{ \\'my-class\\': true }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': false }),\n        }),\n        ' :class=\"{ \\'my-class\\': false }\" ',\n      ),\n      '\\n',\n    ])\n  }\n}\n```\n\n:::\n\n## v-on\n\n## 今回目指す開発者インタフェース\n\nそれでは本題の v-on の実装に移っていきましょう．\n\nv-on もまた，さまざまな開発者インタフェースを持っています．  \nhttps://vuejs.org/guide/essentials/event-handling.html\n\n今回目指すものはざっとこんな感じでしょうか．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    const increment = (e: Event) => {\n      console.log(e)\n      count.value++\n    }\n    return { count, increment, state: { increment }, eventName: 'click' }\n  },\n\n  template: `<div>\n    <p>count: {{ count }}</p>\n\n    <button v-on:click=\"increment\">v-on:click=\"increment\"</button>\n    <button v-on:[eventName]=\"increment\">v-on:click=\"increment\"</button>\n    <button @click=\"increment\">@click=\"increment\"</button>\n    <button v-on=\"{ click: increment }\">v-on=\"{ click: increment }\"</button>\n\n    <button @click=\"state.increment\">v-on:click=\"increment\"</button>\n    <button @click=\"count++\">@click=\"count++\"</button>\n    <button @click=\"() => count++\">@click=\"() => count++\"</button>\n    <button @click=\"increment($event)\">@click=\"increment($event)\"</button>\n    <button @click=\"e => increment(e)\">@click=\"e => increment(e)\"</button>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n## やりたいこと\n\n実は Parser の実装としては，前チャプターのもので十分で，問題は Transformer の実装です．  \n主に arg 有無と，exp の形式の種類によって変換する内容が変わってきます．\nそして，arg がない場合に関して，やることはほとんど v-bind と同じです．\n\nつまり考えるべきは，arg がある場合の exp として取りうる形式の種類と，それらに必要な AST Node の変換です．\n\n- 課題 1  \n  関数を割り当てられる  \n  こちらは最もシンプルなケースです．\n\n  ```html\n  <button v-on:click=\"increment\">increment</button>\n  ```\n\n- 課題 2  \n  その場で関数式を書ける\n  この場合には，第 1 引数としてイベントを受け取ることができます．\n\n  ```html\n  <button v-on:click=\"(e) => increment(e)\">increment</button>\n  ```\n\n- 課題 3  \n  関数以外の文を書ける\n\n  ```html\n  <button @click=\"count = 0\">reset</button>\n  ```\n\n  この式は以下のような関数に変換する必要があるようです．\n\n  ```ts\n  ;() => {\n    count = 0\n  }\n  ```\n\n- 課題 4  \n  課題 3 のような場合には `$event` という識別子が使える\n  こちらはイベントオブジェクトを扱うケースです．\n\n  ```ts\n  const App = defineComponent({\n    setup() {\n      const count = ref(0)\n      const increment = (e: Event) => {\n        console.log(e)\n        count.value++\n      }\n      return { count, increment, object }\n    },\n\n    template: `\n      <div class=\"container\">\n        <button @click=\"increment($event)\">increment($event)</button>\n        <p> {{ count }} </p>\n      </div>\n      `,\n  })\n  // @click=\"() => increment($event)\" のようには使えない。\n  ```\n\n  以下のような関数に変換する必要があるようです．\n\n  ```ts\n  $event => {\n    increment($event)\n  }\n  ```\n\n## 実装\n\n### arg がない場合\n\nとりあえず，arg が存在しない場合については，v-bind と同じなので，そこから実装してみます．  \n前チャプターで TODO コメントを残していた部分です．transformElement の以下のあたりです．\n\n```ts\nconst isVBind = name === 'bind'\nconst isVOn = name === 'on' // --------------- これ\n\n// special case for v-bind and v-on with no argument\nif (!arg && (isVBind || isVOn)) {\n  if (exp) {\n    if (isVBind) {\n      pushMergeArg()\n      mergeArgs.push(exp)\n    } else {\n      // -------------------------------------- ここ\n      // v-on=\"obj\" -> toHandlers(obj)\n      pushMergeArg({\n        type: NodeTypes.JS_CALL_EXPRESSION,\n        loc,\n        callee: context.helper(TO_HANDLERS),\n        arguments: [exp],\n      })\n    }\n  }\n  continue\n}\n\nconst directiveTransform = context.directiveTransforms[name]\nif (directiveTransform) {\n  const { props } = directiveTransform(prop, node, context)\n  if (isVOn && arg && !isStaticExp(arg)) {\n    pushMergeArg(createObjectExpression(props, elementLoc))\n  } else {\n    properties.push(...props)\n  }\n} else {\n  // TODO: custom directive.\n}\n```\n\n`TO_HANDLERS` と言う helper 関数に関しては今回新たに実装します．\n\n`v-on=\"{ click: increment }\"` のような形式で渡ってきたオブジェクトを，`{ onClick: increment }` のような形式に変換する関数です．  \n特に難しいところはないかと思います．\n\n```ts\nimport { toHandlerKey } from '../../shared'\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {}\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key]\n  }\n  return ret\n}\n```\n\nこれで arg がない場合の実装は終わりです．  \n問題の arg がある場合の実装に移ります．\n\n### transformVOn\n\nさて，今回のメインテーマです．v-on の exp には様々な形式があります．\n\n```ts\nincrement\n\nstate.increment\n\ncount++\n\n;() => count++\n\nincrement($event)\n\ne => increment(e)\n```\n\nまず，これらの形式は大きく二つに分類できます．「関数」と「文」です．  \nVue では，単体の Identifier か，単体の MemberExpression, 関数式 の場合には関数として扱われます．  \nそれ以外が文です．ソースコード上は inlineStatement という名前で通じているようです．\n\n```ts\n// function (※ 便宜上セミコロンがついてしまっていますが、これらは関数式だと思ってください。)\nincrement\nstate.increment\n;() => count++\ne => increment(e)\n\n// inlineStatement\ncount++\nincrement($event)\n```\n\nつまり，今回実装する流れ的には，\n\n1. まずは関数かどうかを判定する (単体の Identifier or 単体の MemberExpression or 関数式)\n\n2-1. 関数であった場合には特に何も変形せずに `eventName: exp` という形式で ObjectProperty を生成する\n\n2-2. 関数でなかった場合 (inlineStatement だった場合) ， <span v-pre> `$event => { ${exp} }`</span> という形式に変換し，ObjectProperty を生成する\n\nといった感じになります．\n\n#### 関数式か，文かの判定\n\nとりあえず，判定の実装を行ってみましょう．\n関数式であるかどうかは正規表現で行っています．\n\n```ts\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/\n\nconst isFn = fnExpRE.test(exp.content)\n```\n\nそして，単体の Identifier か，単体の MemberExpression かどうかは，`isMemberExpression` と言う関数で実装されています．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\n```\n\nこの，`isMemberExpression` は結構泥臭く，長々と実装してあります．ちょっと長いので，ここでは省略します．(ぜひコードを読んでみてください．)  \nMemberExpression というと，`parent.prop`のような形式を推察しますが，どうやらこの関数では `ident` のようなルートレベルのものも true として判定しているようです．\n\nここまで判定できたら，inlineStatement である条件はこれら以外のものですから，以下のような判定になります．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isFnExp = fnExpRE.test(exp.content)\nconst isInlineStatement = !(isMemberExp || isFnExp)\n```\n\nこれで判定することはできたので，この結果を元に変換処理を実装していきます．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))\nconst hasMultipleStatements = exp.content.includes(`;`)\n\nif (isInlineStatement) {\n  // wrap inline statement in a function expression\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\n### 問題点\n\n実は上記の対応では少し問題点があります．\n\nというのも，`dir.exp` 中では setup からバインドされた値を扱うので processExpression を噛ませないといけないのですが，問題は `$event` です．  \nAST 上は `$event` も Identifier 扱いなので，このままでは `_ctx.` prefix がついてしまいます．\n\nそこで少し工夫をしてみます．\ntransformContext に ローカル変数を登録するようにします．そして，walkIdentifiers の方では，ローカル変数がある場合には onIdentifier を実行しないようにします．\n\n```ts\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  identifiers: Object.create(null),\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      addId(exp)\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      removeId(exp)\n    }\n  },\n}\n\nfunction addId(id: string) {\n  const { identifiers } = context\n  if (identifiers[id] === undefined) {\n    identifiers[id] = 0\n  }\n  identifiers[id]!++\n}\n\nfunction removeId(id: string) {\n  context.identifiers[id]!--\n}\n```\n\n```ts\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null), // [!code ++]\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        const isLocal = !!knownIds[node.name] // [!code ++]\n        // prettier-ignore\n        if (!isLocal) { // [!code ++]\n          onIdentifier(node);\n        } // [!code ++]\n      }\n    },\n  })\n}\n```\n\nprocessExpression で walkIdentifiers を利用する際に context から identifiers を引っ張ってきます．\n\n```ts\nconst ids: QualifiedId[] = []\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers) // [!code ++]\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // [!code ++]\n)\n```\n\nあとは，transformOn で transform する際に `$event` を登録してあげます．\n\n```ts\n// prettier-ignore\nif (!context.isBrowser) { // [!code ++]\n  isInlineStatement && context.addIdentifiers(`$event`); // [!code ++]\n  exp = dir.exp = processExpression(exp, context); // [!code ++]\n  isInlineStatement && context.removeIdentifiers(`$event`); // [!code ++]\n} // [!code ++]\n\nif (isInlineStatement) {\n  // wrap inline statement in a function expression\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\nこのように v-on は割と特殊な対応が必要なので，transformOn で個別で対応するという都合上，transformExpression の方ではスキップするようにします．\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  // .\n  // .\n  // .\n  if (\n    exp &&\n    exp.type === NodeTypes.SIMPLE_EXPRESSION &&\n    !(dir.name === 'on' && arg) // [!code ++]\n  ) {\n    dir.exp = processExpression(exp, ctx)\n  }\n}\n```\n\nさて，ここまでで今回のキモは終わりです．残り必要な部分を実装して v-on を完成させましょう！！\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/025_v_on)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/027-event-modifier.md",
    "content": "# イベント修飾子\n\n## 今回やること\n\n前回，v-on ディレクティブを実装したので続いてはイベント修飾子を実装します．\n\nVue.js には preventDefault や stopPropagation に対応する修飾子があります．\n\nhttps://ja.vuejs.org/guide/essentials/event-handling.html#event-modifiers\n\n今回は以下のような開発者インターフェースを目指してみましょう．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const inputText = ref('')\n\n    const buffer = ref('');\n    const handleInput = (e: Event) => {\n      const target = e.target as HTMLInputElement\n      buffer.value = target.value\n    }\n    const submit = () => {\n      inputText.value = buffer.value\n      buffer.value = ''\n    };\n\n    return { inputText, buffer, handleInput,fun submit }\n  },\n\n  template: `<div>\n    <form @submit.prevent=\"submit\">\n      <label>\n        Input Data\n        <input :value=\"buffer\" @input=\"handleInput\" />\n      </label>\n      <button>submit</button>\n    </form>\n    <p>inputText: {{ inputText }}</p>\n</div>`,\n});\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n特に，以下の部分に注目してください．\n\n```html\n<form @submit.prevent=\"submit\"></form>\n```\n\n`@submit.prevent` という記述があります．これは submit イベントのハンドラを呼び出す際に，`preventDefault` を実行するという意味です．\n\nこの `.prevent` を記述しない場合，submit 時にページがリロードされてしまいます．\n\n## AST と Parser の実装\n\nテンプレートの新しいシンタックスを追加するわけなので，Parser と AST の変更が必要になります．\n\nまずは AST を見てみましょう．これはとっても簡単で，`DirectiveNode` に `modifiers` というプロパティ(string の配列)を追加するだけです．\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined\n  arg: ExpressionNode | undefined\n  modifiers: string[] // ここを追加\n}\n```\n\nこれに合わせて Parser も実装します．\n\n実は本家から拝借した正規表現にもう含まれているので，こちらの実装もとても簡単です．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  const modifiers = match[3] ? match[3].slice(1).split('.') : [] // match 結果から修飾子を取り出す\n  return {\n    type: NodeTypes.DIRECTIVE,\n    name: dirName,\n    exp: value && {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      content: value.content,\n      isStatic: false,\n      loc: value.loc,\n    },\n    loc,\n    arg,\n    modifiers, // return に含める\n  }\n}\n```\n\nはい．これで AST と Parser の実装は完了です．\n\n## compiler-dom/transform\n\nここで少し今のコンパイラの構成をおさらいしてみます．\n\n現状は以下のような構成になっています．\n\n![Compiler architecture before DOM modifiers](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-core-only.svg)\n\ncompiler-core と compiler-dom のそれぞれの役割を改めて理解してみると，  \ncompiler-core は DOM に依存しないコンパイラの機能を提供するもので，AST の生成や，その変換を行います．\n\nこれまでに，v-on ディレクティブなどを compiler-core に実装しましたが，これは`@click=\"handle\"` という記述を `{ onClick: handle }` というオブジェクトに変換しているだけで，  \nDOM に依存するような処理は行っていません．\n\nここで，今回実装したいものを見てみましょう．  \n今回は実際に `e.preventDefault()` や `e.stopPropagation()` を実行するコードを生成したいです．  \nこれらは大きく DOM に依存してしまいます．\n\nそこで，compiler-dom 側にも transformer を実装していきます． DOM に関連する transform はここに実装して行くことにしましょう．\n\ncompiler-dom の方に `transformOn` を実装していきたいのですが，runtime-core の `transformOn` との兼ね合いを考える必要があります．  \n兼ね合いというのは，「compiler-core の transform も実行しつつ，compiler-dom で実装した transform を実装するにはどうすればいいのか?」 ということです．\n\nそこでまず， compiler-core の方に実装してある `DirectiveTransform` という interface に手を加えていきます．\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult, // 追加\n) => DirectiveTransformResult\n```\n\naugmentor というものを追加してみました．  \nまぁ，これはただのコールバック関数です． `DirectiveTransform` の interface としてコールバックを受け取れるようにして，transform 関数を拡張可能にしています．\n\ncompiler-dom の方では，compiler-core で実装した transformer をラップした transformer の実装をしていくようにします．\n\n```ts\n// 実装イメージ\n\n// compiler-dom側の実装\n\nimport { transformOn as baseTransformOn } from 'compiler-core'\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransformOn(dir, node, context, () => {\n    /** ここに compiler-dom の独自の実装 */\n    return {\n      /** */\n    }\n  })\n}\n```\n\nそして，この compiler-dom 側で実装した `transformOn` を compiler のオプションとして渡してあげれば OK です．  \n以下のような関係図です．  \n全ての transformer を compiler-dom から渡すのではなく，デフォルトの実装は compiler-core に実装しておき，オプションとしてあと乗せ出来るような構成にするイメージです．\n\n![Compiler architecture with DOM augmentor](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-dom-augmentor.svg)\n\nこれで compiler-core が DOM に依存せず，compiler-dom 側で DOM に依存した処理を実装しつつ compiler-core の transformer を実行できるようになります．\n\n## transformer の実装\n\nそれでは，compiler-dom 側の transformer を実装していきます．\n\nどういう風に transform していきましょうか．とりあえず，一概に '修飾子' といってもいろんな種類のものがあるので，  \n今後のことも考えて分類わけできるようにしておきましょう．\n\n今回実装するのは 'イベント修飾子' です．\nとりあえず，この eventModifiers として取り出してみましょう．\n\n```ts\nconst isEventModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self`,\n)\n\nconst resolveModifiers = (modifiers: string[]) => {\n  const eventModifiers = []\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i]\n    if (isEventModifier(modifier)) {\n      eventModifiers.push(modifier)\n    }\n  }\n\n  return { eventModifiers }\n}\n```\n\neventModifiers を抽出できたところでこれをどう使いましょうか．\n結論から言うと，これは runtime-dom 側に withModifiers というヘルパー関数を実装し，その関数を呼び出す式に transform していきます．\n\n```ts\n// runtime-dom/runtimeHelpers.ts\n\nexport const V_ON_WITH_MODIFIERS = Symbol()\n```\n\n```ts\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, baseResult => {\n    const { modifiers } = dir\n    if (!modifiers.length) return baseResult\n\n    let { key, value: handlerExp } = baseResult.props[0]\n    const { eventModifiers } = resolveModifiers(modifiers)\n\n    if (eventModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(eventModifiers),\n      ])\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    }\n  })\n}\n```\n\nこれで transform 側の実装は概ね終わりです．\n\nあとはこの withModifiers を compiler-dom 側で実装していきます．\n\n## withModifiers の実装\n\nruntime-dom/directives/vOn.ts に実装を進めていきます．\n\n実装はとてもシンプルです．\n\nイベント修飾子のガード関数を実装して，配列で受け取った修飾子の分だけ実行するような実装をするだけです．\n\n```ts\nconst modifierGuards: Record<string, (e: Event) => void | boolean> = {\n  stop: e => e.stopPropagation(),\n  prevent: e => e.preventDefault(),\n  self: e => e.target !== e.currentTarget,\n}\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]]\n      if (guard && guard(event)) return\n    }\n    return fn(event, ...args)\n  }\n}\n```\n\nこれで実装はおしまいです．\n\n動作を確認してみましょう！  \nボタンを押した際に，ページがリロードされずに input の内容が画面に反映されていれば OK です！\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier)\n\n## その他の修飾子\n\nさて，ここまできたら他の修飾子も実装してみましょう．\n\n基本的な実装方針は同じです．\n\n修飾子を以下のように分類してみましょう．\n\n```ts\nconst keyModifiers = []\nconst nonKeyModifiers = []\nconst eventOptionModifiers = []\n```\n\nあとはこれに必要な map を生成して，resolveModifiers でこれらに分類できれば OK です．\n\n残り気をつけるべき点は 2 点で，\n\n- 修飾子名と実際の DOM API の名前の差異\n- 特定のキーイベントで実行する helper 関数を新たに実装 (withKeys)\n\nです．\n\nこの辺りは実際にコードを読みながら実装してみてください！  \nここまできた皆さんなら出来るはずです．\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier2)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/030-fragment.md",
    "content": "# Fragment を実装する\n\n## 今の実装の問題点\n\n以下のようなコードを playground で実行してみましょう．\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n以下のようなエラーが出てしまうかと思います．\n\n![Fragment error result in the browser](/figures/50-basic-template-compiler/fragment/fragment-error-result.png)\n\nエラー文をみてみると， Function コンストラクタで起きているようです．\n\nつまり，codegen までは一応成功しているようなので，実際にどのようなコードが生成されたのかみてみましょう．\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode } = ChibiVue\n\n    return _createVNode(\"header\", null, \"header\")\"\\n  \"_createVNode(\"main\", null, \"main\")\"\\n  \"_createVNode(\"footer\", null, \"footer\")\n   }\n}\n```\n\nreturn の先がおかしなことになってしまっていますね．今の codegen の実装だと，ルートが配列だった場合(単一のノードではない場合)を考慮できていません．  \n今回はこれを修正していきます．\n\n## どういうコードを生成すればいいのか\n\n修正していくとはいえ，どういうコードを生成できるようになれば良いでしょうか．\n\n結論から言うと以下のようなコードになります．\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode, Fragment: _Fragment } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      [\n        _createVNode('header', null, 'header'),\n        '\\n  ',\n        _createVNode('main', null, 'main'),\n        '\\n  ',\n        _createVNode('footer', null, 'footer'),\n      ],\n    ])\n  }\n}\n```\n\nこの `Fragment` というものは Vue で定義されている symbol です．  \nつまり，Fragment は FragmentNode のような AST として表現されるものではなく，単に ElementNode の tag として表現されます．\n\nそして，tag が Fragment あった場合の処理を renderer に実装します．  \nText と似たよう感じです．\n\n## 実装していく\n\nfragment の symbol は runtime-core/vnode.ts の方に実装されます．\n\nVNodeTypes の新たな種類として追加しましょう．\n\n```ts\nexport type VNodeTypes =\n  | Component; // `object` になってると思うので、ついでに直しておきました\n  | typeof Text\n  | typeof Fragment  // これを追加\n  | string\n\nexport const Fragment = Symbol(); // これを追加\n```\n\nrenderer を実装します．\n\npatch 関数に fragment の時の分岐を追加します．\n\n```ts\nif (type === Text) {\n  processText(n1, n2, container, anchor)\n} else if (shapeFlag & ShapeFlags.ELEMENT) {\n  processElement(n1, n2, container, anchor, parentComponent)\n} else if (type === Fragment) {\n  // ここ\n  processFragment(n1, n2, container, anchor, parentComponent)\n} else if (shapeFlag & ShapeFlags.COMPONENT) {\n  processComponent(n1, n2, container, anchor, parentComponent)\n} else {\n  // do nothing\n}\n```\n\n注意点としては，要素の insert や remove は基本的に anchor を目印に実装して行く必要があることです．\n\nanchor というのは名の通り，フラグメントの開始と終了の位置を示すものです．\n\n始端の要素 は 従来から VNode に存在する `el` というプロパティが担いますが，現時点だと終端を表すプロパティが存在しないので追加します．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  anchor: HostNode | null // fragment anchor // 追加\n  // .\n  // .\n}\n```\n\nmount 時に anchor を設定します\n\nそして，mount / patch に anchor として フラグメントの終端を渡してあげます．\n\n```ts\nconst processFragment = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!\n  const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!\n\n  if (n1 == null) {\n    hostInsert(fragmentStartAnchor, container, anchor)\n    hostInsert(fragmentEndAnchor, container, anchor)\n    mountChildren(\n      n2.children as VNode[],\n      container,\n      fragmentEndAnchor,\n      parentComponent,\n    )\n  } else {\n    patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent)\n  }\n}\n```\n\n更新時，fragment の要素が変動する際も注意します．\n\n```ts\nconst move = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const { type, children, el, shapeFlag } = vnode\n\n  // .\n  // .\n\n  if (type === Fragment) {\n    hostInsert(el!, container, anchor)\n    for (let i = 0; i < (children as VNode[]).length; i++) {\n      move((children as VNode[])[i], container, anchor)\n    }\n    hostInsert(vnode.anchor!, container, anchor) // アンカーを挿入\n    return\n  }\n  // .\n  // .\n  // .\n}\n```\n\nunmount 時も anchor を頼りに要素を削除していきます．\n\n```ts\nconst remove = (vnode: VNode) => {\n  const { el, type, anchor } = vnode\n  if (type === Fragment) {\n    removeFragment(el!, anchor!)\n  }\n\n  // .\n  // .\n  // .\n}\n\nconst removeFragment = (cur: RendererNode, end: RendererNode) => {\n  let next\n  while (cur !== end) {\n    next = hostNextSibling(cur)! // ※ nodeOps に追加しましょう！\n    hostRemove(cur)\n    cur = next\n  }\n  hostRemove(end)\n}\n```\n\n## 動作を見てみる\n\n先ほどのコードはきちんと動くようになっているはずです．\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n現状だと，v-for ディレクティブなどが使えないことから，template で fragment を使いつつ要素の個数を変化させるような記述ができないので，\n\n擬似的に コンパイル後のコードを書いて動作を見てみましょう．\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\n// const App = defineComponent({\n//   template: `<header>header</header>\n//   <main>main</main>\n//   <footer>footer</footer>`,\n// });\n\nconst App = defineComponent({\n  setup() {\n    const list = ref([0])\n    const update = () => {\n      list.value = [...list.value, list.value.length]\n    }\n    return () =>\n      h(Fragment, {}, [\n        h('button', { onClick: update }, 'update'),\n        ...list.value.map(i => h('div', {}, i)),\n      ])\n  },\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nちゃんと動作しているようです！\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/030_fragment)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/035-comment.md",
    "content": "# コメントアウトを実装する\n\n## 目指す開発者インタフェース\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `\n  <!-- this is header. -->\n  <header>header</header>\n\n  <!-- \n    this is main.\n    main content is here!\n  -->\n  <main>main</main>\n\n  <!-- this is footer -->\n  <footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n特に説明は必要ないでしょう．\n\n## AST とパーサの実装\n\nコメントアウトをどう実装するかですが，一見 パースするときに無視してしまえばいい感じもします．\n\nしかし，Vue のコメントアウトは，template に記述したものがそのまま HTML として出力されるようになっています．\n\nつまりはコメントアウトもレンダリングする必要があるので，VNode 上での表現が必要かつコンパイラもそのコードを出力する必要があり，  \nその上コメントノードを生成する操作も必要になります．\n\nまずは AST とパーサを実装していきましょう．\n\n### AST\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  COMMENT,\n  // .\n  // .\n  // .\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT\n  content: string\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n```\n\n### Parser\n\nエラーはとりあえず throw するようにします．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  // .\n  // .\n  // .\n  if (startsWith(s, '{{')) {\n    node = parseInterpolation(context)\n  } else if (s[0] === '<') {\n    if (s[1] === '!') {\n      // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n      if (startsWith(s, '<!--')) {\n        node = parseComment(context)\n      }\n    } else if (/[a-z]/i.test(s[1])) {\n      node = parseElement(context, ancestors)\n    }\n  }\n  // .\n  // .\n  // .\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context)\n  let content: string\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source)\n  if (!match) {\n    content = context.source.slice(4)\n    advanceBy(context, context.source.length)\n    throw new Error('EOF_IN_COMMENT') // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error('ABRUPT_CLOSING_OF_EMPTY_COMMENT') // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error('INCORRECTLY_CLOSED_COMMENT') // TODO: error handling\n    }\n    content = context.source.slice(4, match.index)\n\n    const s = context.source.slice(0, match.index)\n    let prevIndex = 1,\n      nestedIndex = 0\n    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1)\n      if (nestedIndex + 4 < s.length) {\n        throw new Error('NESTED_COMMENT') // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1)\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n## コードを生成する\n\nruntime-core 側に Comment を表現する VNode を追加します．\n\n```ts\nexport const Comment = Symbol()\nexport type VNodeTypes =\n  | string\n  | Component\n  | typeof Text\n  | typeof Comment\n  | typeof Fragment\n```\n\ncreateCommentVNode という関数を実装し，helper として公開します．\n\ncodegen ではこの createCommentVNode を呼び出すコードを生成します．\n\n```ts\nexport function createCommentVNode(text: string = ''): VNode {\n  return createVNode(Comment, null, text)\n}\n```\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    // .\n    // .\n    // .\n    case NodeTypes.COMMENT:\n      genComment(node, context)\n      break\n    // .\n    // .\n    // .\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)\n}\n```\n\n## レンダリングする\n\nrenderer の実装をやっていきます．\n\nいつものように patch で Comment の場合を分岐し，mount 時にコメントを生成します．\n\npatch に関しては，今回は静的なものなので，特に何も行いません．(コード上はそのまま代入するようにしています．)\n\n```ts\nconst patch = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const { type, ref, shapeFlag } = n2\n  if (type === Text) {\n    processText(n1, n2, container, anchor)\n  } else if (type === Comment) {\n    processCommentNode(n1, n2, container, anchor)\n  } //.\n  //.\n  //.\n}\n\nconst processCommentNode = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  if (n1 == null) {\n    hostInsert(\n      (n2.el = hostCreateComment((n2.children as string) || '')), // hostCreateComment を nodeOps 側に実装しましょう！\n      container,\n      anchor,\n    )\n  } else {\n    n2.el = n1.el\n  }\n}\n```\n\nさて，ここまででコメントアウトが実装できたはずです．実際に動作を確認してみましょう！\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/035_comment)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/040-v-if-and-structural-directive.md",
    "content": "# v-if と構造的ディレクティブ\n\nさてここからはまたディレクティブの実装をやっていきましょう！\n\nついに v-if を実装していきます．\n\n## v-if ディレクティブとこれまでのディレクティブの違い\n\nこれまでの v-bind や v-on などを実装してきました．\n\nこれから v-if を実装して行くのですが，v-if はこれらのディレクティブとは少し作りが違います．\n\n以下は Vue.js の公式ドキュメントのコンパイル時最適化に関する項目の一説ですが，\n\n> In this case, the entire template has a single block because it does not contain any structural directives like v-if and v-for.\n\nhttps://vuejs.org/guide/extras/rendering-mechanism.html#tree-flattening\n\nという言葉が見受けられます．(Tree Flattening が何かについては別で解説するので気にしなくていいです．)\n\nこの通り，v-if や v-if は `structural directives` と呼ばれるもので，構造を伴うディレクティブです．\n\nangular のドキュメントだと項目として明記されていたりもします．\n\nhttps://angular.jp/guide/structural-directives\n\nv-if や v-for は単にその要素の属性(+イベントに対する振舞い)を変更するだけでなく，要素の存在を切り替えたり，リストの数に応じて 要素を生成・削除したりと，要素の構造を変更するディレクティブです．\n\n## 目指す開発者インターフェース\n\nv-if / v-else-if / v-else を組み合わせて FizzBuzz が実装できるようなものを考えてみましょう．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const n = ref(1)\n    const inc = () => {\n      n.value++\n    }\n\n    return { n, inc }\n  },\n\n  template: `\n    <button @click=\"inc\">inc</button>\n    <p v-if=\"n % 5 === 0 && n % 3 === 0\">FizzBuzz</p>\n    <p v-else-if=\"n % 5 === 0\">Buzz</p>\n    <p v-else-if=\"n % 3 === 0\">Fizz</p>\n    <p v-else>{{ n }}</p>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n今回はまず初めに，どういうコードを生成したいかについても考えてみようかと思います．\n\n結論から言ってしまうと，v-if や v-else は以下のように条件式に変換されます．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\nこれをみても分かる通り，これまで実装してきたものに条件を表す構造を持たせています．\n\nこのようなコードを生成するための AST に変形させる transformer を実装するには一工夫必要そうです．\n\n::: warning\n\n現時点での実装では空白などの読み飛ばしの実装を行っていないので実際には間に余計な文字 Node が入ってしまうかと思います．\n\nが， v-if の実装上は特に問題ありませんので(後でわかります)，今回は無視してください．\n\n:::\n\n## 構造的ディレクティブの実装\n\n### 構造にまつわるメソッドを実装する\n\nv-if の実装を行っていく前に，少し準備です．\n\n最初にも v-if や v-for という構造的ディレクティブは AST Node の構造を変更するディレクティブであるということを説明しました．\n\nこれらを実現するために，ベースとなる transformer にいくつかの実装を行います．\n\n具体的には，TransformContext に以下の３つを実装します．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  replaceNode(node: TemplateChildNode): void // 追加\n  removeNode(node?: TemplateChildNode): void // 追加\n  onNodeRemoved(): void // 追加\n}\n```\n\ntraverseChildren の方で現在の parent と children の index は保持するようになっていると思うので，それらを使って 上記のメソッドを実装していきます．\n\n<!-- NOTE: このチャプターまで実装しなくてもいいかもしれない -->\n\n::: details 念の為\n\nこの部分です．\n\n実装していると思いますが，これを実装したチャプターではあまり詳しく説明していなかったので念の為補足です．\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent // これ\n    context.childIndex = i // これ\n    traverseNode(child, context)\n  }\n}\n```\n\n:::\n\n```ts\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n\n    // Node を受け取って currentNode と 該当の parent の children をその Node に置き換えます\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node\n    },\n\n    // Node を受け取って currentNode と 該当の parent の children からその Node を削除します\n    removeNode(node) {\n      const list = context.parent!.children\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null\n        context.onNodeRemoved()\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--\n          context.onNodeRemoved()\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1)\n    },\n\n    // こちらは replaceNode 等を実際に使用する際に登録するようにします\n    onNodeRemoved: () => {},\n  }\n\n  return context\n}\n```\n\n既存の実装も少し修正が必要です．transform 中に removeNode が呼ばれることを想定して，traverseChildren の方を調整してあげます．\n\nNode が削除されると index が変わってしまうので，Node が 削除された際に for の index を減らしてあげます．\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  let i = 0 // これ\n  const nodeRemoved = () => {\n    i-- // これ\n  }\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    context.onNodeRemoved = nodeRemoved // これ\n    traverseNode(child, context)\n  }\n}\n```\n\n### createStructuralDirectiveTransform の実装\n\nv-if や v-for といったディレクティブを実装するにあたって，createStructuralDirectiveTransform というヘルパー関数を実装します．\n\nこれらの transformer は NodeTypes.ELEMENT のみに作用し，その Node が持つ DirectiveNode に対して 各 transformer の実装を適用します．\n\nまぁ，実装自体は大きなものではないので，実際に見てもらった方がわかりやすいと思います．以下のような感じです．\n\n```ts\n// 各 transformer (v-if/v-for など) はこの interface にそって実装されます。\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void)\n\nexport function createStructuralDirectiveTransform(\n  // name は正規表現にも対応しています。\n  // v-if の transformer でいうと、 `/^(if|else|else-if)$/` のようなものを受け取れる想定です。\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name)\n    ? (n: string) => n === name\n    : (n: string) => name.test(n)\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      // NodeTypes.ELEMENT のみに作用\n      const { props } = node\n      const exitFns = []\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i]\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          // NodeTypes.DIRECTIVE かつ name が一致するものに対して transformer を実行\n          props.splice(i, 1)\n          i--\n          const onExit = fn(node, prop, context)\n          if (onExit) exitFns.push(onExit)\n        }\n      }\n      return exitFns\n    }\n  }\n}\n```\n\n## v-if を実装していく\n\n### AST の実装\n\n上記までで準備は終わりです．ここからは v-if の実装を行っていきます．\n\nいつものように，AST の定義から行なって，パーサーを実装していきましょう．\n\nと言いたいところでしたが，今回はパーサーは必要なさそうです．\n\nどちらかというと，今回は transform 後の AST をどういう形にしたいかを考えて，それに変形させるための transformer を実装していく感じになります．\n\n改めて，冒頭で想定していたコンパイル後のコードを見てみましょう．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\n最終的には条件式(三項演算子)に変換されていることが分かります．\n\nこれまで条件式を扱ったことはないので，Codegen のために AST 側でこれを取り扱う必要があるようです．  \n基本的に考えたいものは 3 つの情報です．(\"三項\"演算子なのでね)\n\n- **条件**  \n  A ? B : C の A にあたる部分です．  \n  condition という名前で表現されます．\n- **条件にマッチした時の Node**  \n  A ? B : C の B にあたる部分です．  \n  consequent という名前で表現されます．\n- **条件にマッチしなかった時の Node**  \n  A ? B : C の C にあたる部分です．  \n  alternate という名前で表現されます．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION\n  test: JSChildNode\n  consequent: JSChildNode\n  alternate: JSChildNode\n  newline: boolean // これは codegen のフォーマット用なのであまり気にしなくていいです\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression // 追加\n  | ExpressionNode\n\nexport function createConditionalExpression(\n  test: ConditionalExpression['test'],\n  consequent: ConditionalExpression['consequent'],\n  alternate: ConditionalExpression['alternate'],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  }\n}\n```\n\nこれらを使って VIf の Node を表現する AST を実装していきます．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  IF,\n  IF_BRANCH,\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF\n  branches: IfBranchNode[]\n  codegenNode?: IfConditionalExpression\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall\n  alternate: VNodeCall | IfConditionalExpression\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH\n  condition: ExpressionNode | undefined // else\n  children: TemplateChildNode[]\n  userKey?: AttributeNode | DirectiveNode\n}\n\nexport type ParentNode =\n  | RootNode\n  | ElementNode\n  // 追加\n  | IfBranchNode\n```\n\n### transformer の実装\n\nAST ができたので，実際にこの AST を生成する transformer を実装していきます．\n\nイメージ的にはいくつかの `ElementNode` をもとに `IfNode` を生成するという感じです．\n\n「いくつかの」といったのは，今回の場合，ある一つの Node を違う一つの Node に変形するようなものではなく，\n\n```html\n<p v-if=\"n % 5 === 0 && n % 3 === 0\">FizzBuzz</p>\n<p v-else-if=\"n % 5 === 0\">Buzz</p>\n<p v-else-if=\"n % 3 === 0\">Fizz</p>\n<p v-else>{{ n }}</p>\n```\n\nのような複数の ElementNode があった場合には v-if ~ v-else までを一つの IfNode として生成する必要があります．\n\n最初の v-if にマッチした場合，後続の Node が v-else-if や v-else に当たるものでないかどうかを判定しながら IfNode を生成していきます．\n\n具体的な それぞれの処理は processIf という関数に逃しておくとして，とりあえず大枠を実装してみましょう．\n先ほどの `createStructuralDirectiveTransform` を活用します．\n\n具体的には，最終的に codegenNode に先ほど実装した AST を詰めていきたいわけなので，\nこの transformer の `onExit` で Node を生成します．\n\n```ts\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!)\n          parentCondition.alternate = createCodegenNodeForBranch(\n            branch,\n            context,\n          )\n        }\n      }\n    })\n  },\n)\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // TODO:\n}\n```\n\n```ts\n/// codegenNode を生成するのに使用した関数たち\n\n// branch の codegenNode 生成\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      // alternate はとりあえずコメントアウトで生成するようになっています。\n      // v-else-if や v-if が来た時に alternate を対象の Node に書き換えます。\n      // `parentCondition.alternate = createCodegenNodeForBranch(branch, context);` の部分です\n      // もし、v-else-if や v-else がこなかった場合には、このまま CREATE_COMMENT の Node になります。\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', 'true']),\n    ) as IfConditionalExpression\n  } else {\n    return createChildrenCodegenNode(branch, context)\n  }\n}\n\nfunction createChildrenCodegenNode(\n  branch: IfBranchNode,\n  context: TransformContext,\n): VNodeCall {\n  // branch から vnode call を取り出すだけ\n  const { children } = branch\n  const firstChild = children[0]\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall\n  return vnodeCall\n}\n\nfunction getParentCondition(\n  node: IfConditionalExpression,\n): IfConditionalExpression {\n  // node から辿って 末端の Node を取得する\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate\n      } else {\n        return node\n      }\n    }\n  }\n}\n```\n\n`processIf` ではより具体的な AST Node の変形処理を行なっていきます．\n\nif / else-if / else の場合がありますが，まずは `if` だった場合を考えてみます．\n\nこちらはかなり単純です． IfNode を生成し，codegenNode の生成を実行してあげるだけです．\nこの際，今の Node を IfBranch としてを生成し，それを IfNode に持たせた上で IfNode に replace しておきます．\n\n```\n- parent\n  - currentNode\n\n↓\n\n- parent\n  - IfNode\n    - IfBranch (currentNode)\n```\n\nのように構造を変更するイメージです．\n\n```ts\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // あらかじめ、exp には processExpression を実行しておきます。\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)\n  }\n\n  if (dir.name === 'if') {\n    const branch = createIfBranch(node, dir)\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    }\n    context.replaceNode(ifNode)\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true)\n    }\n  } else {\n    // TODO:\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === 'else' ? undefined : dir.exp,\n    children: [node],\n  }\n}\n```\n\n次は v-if 以外の場合を考えてみましょう．\n\ncontext から parent の children を辿って siblings を取得し，  \n現在の node (自身) から順にループを回し，自身をもとに IfBranch を生成して branches に push していきます．  \nこの際，コメントや空のテキストは削除してしまいます．\n\n```ts\nif (dir.name === 'if') {\n  /** 省略 */\n} else {\n  const siblings = context.parent!.children\n  let i = siblings.indexOf(node)\n  while (i-- >= -1) {\n    const sibling = siblings[i]\n    if (sibling && sibling.type === NodeTypes.COMMENT) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (\n      sibling &&\n      sibling.type === NodeTypes.TEXT &&\n      !sibling.content.trim().length\n    ) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (sibling && sibling.type === NodeTypes.IF) {\n      context.removeNode()\n      const branch = createIfBranch(node, dir)\n      sibling.branches.push(branch)\n      const onExit = processCodegen && processCodegen(sibling, branch, false)\n      traverseNode(branch, context)\n      if (onExit) onExit()\n      context.currentNode = null\n    }\n    break\n  }\n}\n```\n\nこれをみても分かる通り，実は else-if と else は区別していません．\n\nAST 上でも condition がない場合は else というふうな定義にしているので，特に考えることはないのです．  \n(`createIfBranch` の `dir.name === \"else\" ? undefined : dir.exp` の部分で吸収)\n\n重要なのは，`if` であった場合に `IfNode` を生成しておくということで，それ以外はその Node の branches に突っ込んでいけばいいわけです．\n\nここまで来れば transformIf の実装は終わりです．あとは周辺に少しだけ手を加えます．\n\ntraverseNode で，IfNode が持つ branches に対して traverseNode を実行してあげるようにします．\n\nIfBranch も traverseChildren の対象にしてあげます．\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  // .\n  // .\n  // .\n  switch (node.type) {\n    // .\n    // .\n    // 追加\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context)\n      }\n      break\n\n    case NodeTypes.IF_BRANCH: // 追加\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n}\n```\n\nあとは，compiler のオプションとして transformIf を登録してあげるだけです．\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformElement],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\nこれで transformer が実装できました！\n\nあとは codegen を実装すれば，v-if の完成です．もう少しです，頑張りましょう！\n\n### codegen の実装\n\nあとは楽ちんです．ConditionalExpression の Node をもとにコードを生成すれば OK です．\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.IF: // ここを追加するのを忘れずに！\n      genNode(node.codegenNode!, context, option)\n      break\n    // .\n    // .\n    // .\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option)\n      break\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break\n  }\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node\n  const { push, indent, deindent, newline } = context\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context)\n  } else {\n    push(`(`)\n    genNode(test, context, option)\n    push(`)`)\n  }\n  needNewline && indent()\n  context.indentLevel++\n  needNewline || push(` `)\n  push(`? `)\n  genNode(consequent, context, option)\n  context.indentLevel--\n  needNewline && newline()\n  needNewline || push(` `)\n  push(`: `)\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION\n  if (!isNested) {\n    context.indentLevel++\n  }\n  genNode(alternate, context, option)\n  if (!isNested) {\n    context.indentLevel--\n  }\n  needNewline && deindent(true /* without newline */)\n}\n```\n\nいつも通り，AST をもとに条件式を生成しているだけなので特に難しいことはないと思います．\n\n## 完成！！\n\nさてさて，久しぶりに少しファットなチャプターになってしまいましたがこれで v-if の実装は完了です！ (お疲れ様でした．)\n\n実際に動かしてみましょう！！！！\n\nちゃんと動いています！\n\n![v-if FizzBuzz result in the browser](/figures/50-basic-template-compiler/v-if/fizzbuzz-result.png)\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/040_v_if_and_structural_directive)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/050-v-for.md",
    "content": "# v-for ディレクティブに対応する\n\n## 今回目指す開発者インターフェース\n\nさて，ディレクティブ実装の続きです．今回は v-for に対応してみようと思います．\n\nまぁ，Vue.js を触ったことあるみなさんならお馴染みのディレクティブだと思います．\n\nv-for には様々な syntax があります．\n最もベーシックなのは配列をループすることですが，他にも文字列であったりオブジェクトの key, range, などなど様々なものをループできます．\n\nhttps://ja.vuejs.org/guide/essentials/list.html\n\n少し長いですが，今回は以下のような開発者インターフェースを目指してみましょう．\n\n```vue\n<script>\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst genId = () => Math.random().toString(36).slice(2)\n\nconst FRUITS_FACTORIES = [\n  () => ({ id: genId(), name: 'apple', color: 'red' }),\n  () => ({ id: genId(), name: 'banana', color: 'yellow' }),\n  () => ({ id: genId(), name: 'grape', color: 'purple' }),\n]\n\nexport default {\n  setup() {\n    const fruits = ref([...FRUITS_FACTORIES].map(f => f()))\n    const addFruit = () => {\n      fruits.value.push(\n        FRUITS_FACTORIES[Math.floor(Math.random() * FRUITS_FACTORIES.length)](),\n      )\n    }\n    return { fruits, addFruit }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"addFruit\">add fruits!</button>\n\n  <!-- basic -->\n  <ul>\n    <li v-for=\"fruit in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- indexed -->\n  <ul>\n    <li v-for=\"(fruit, i) in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- destructuring -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">{{ name }}</span>\n    </li>\n  </ul>\n\n  <!-- object -->\n  <ul>\n    <li v-for=\"(value, key, idx) in fruits[0]\" :key=\"key\">\n      [{{ idx }}] {{ key }}: {{ value }}\n    </li>\n  </ul>\n\n  <!-- range -->\n  <ul>\n    <li v-for=\"n in 10\">{{ n }}</li>\n  </ul>\n\n  <!-- string -->\n  <ul>\n    <li v-for=\"c in 'hello'\">{{ c }}</li>\n  </ul>\n\n  <!-- nested -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">\n        <span v-for=\"n in 3\">{{ n }}</span>\n        <span>{{ name }}</span>\n      </span>\n    </li>\n  </ul>\n</template>\n```\n\n急にこんなにいっぱい実装するのかよ！無理だろ！と身構えてしまうかもしれませんが，安心してください，ステップバイステップで説明していきます．\n\n## 実装方針\n\nまず，軽くどういうふうにコンパイルしたいのかということを考えてみて，実装する際に難しそうなポイントはどこなのかということについて考えてみましょう．\n\nまず，目指したいコンパイル結果から見てみましょう．\n\n基本的な構成はそれほど難しいものではありません．renderList というヘルパー関数を runtime-core の方に実装して，リストをレンダリングする式にコンパイルします．\n\n例 1:\n\n```html\n<!-- input -->\n<li v-for=\"fruit in fruits\" :key=\"fruit.id\">{{ fruit.name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\n例 2:\n\n```html\n<!-- input -->\n<li v-for=\"(fruit, idx) in fruits\" :key=\"fruit.id\">\n  {{ idx }}: {{ fruit.name }}\n</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\n例 3:\n\n```html\n<!-- input -->\n<li v-for=\"{ name, id } in fruits\" :key=\"id\">{{ name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, ({ name, id }) => h('li', { key: id }, name)),\n)\n```\n\n後々，renderList の第 1 引数として渡す値は配列以外にも数値やオブジェクトも想定していきますが，  \n一旦，配列のみを想定すると，\\_renderList 関数の実装自体は概ね Array.prototype.map と同じようなものだと理解できるかと思います．  \n配列以外の値に関しては，\\_renderList の方で正規化してあげればいいだけなので，初めのうちは忘れてしまいましょう．(配列のことだけ考えてれば OK)\n\nそして，ここまで様々なディレクティブを実装してきたみなさんにとってはこのようなコンパイラ(transformer) を実装するのはさほど難しい事ではないとは思います．\n\n## 実装の肝 (難しいポイント)\n\n問題は，SFC で使用する場合です．  \nSFC で使用する際のコンパイラと，ブラウザ上で使用する際のコンパイラの差異を覚えているでしょうか?  \nそうです．`_ctx` を使った式の解決です．\n\nv-for ではいろんな形でユーザー定義のローカル変数が登場するので，それらをうまく収集して rewriteIdentifiers をスキップしていく必要があります．\n\n```ts\n// ダメな例\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // fruits は _ctx からバインドされるものだので prefix がついていて OK\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: _ctx.id }, // ここに _ctx がついてはダメ\n        _ctx.name, // ここに _ctx がついてはダメ\n      ),\n  ),\n)\n```\n\n```ts\n// 良い例\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // fruits は _ctx からバインドされるものだので prefix がついていて OK\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: id }, // ここに _ctx がついてはダメ\n        name, // ここに _ctx がついてはダメ\n      ),\n  ),\n)\n```\n\nローカル変数の定義は様々で，例 1~3 までそれぞれあります．\n\nそれぞれの定義を解析し，スキップ対象の識別子を収集していく必要があります．\n\nさて，これをどうやって実現していくかについてですが，それは一旦おいておいて，大枠から実装を始めてしまいましょう．\n\n## AST の実装\n\nとりあえず例の如く，AST を定義しておきます．\n\n今回も v-if の時と同様，transform 後の AST を考えていきます．(パーサの実装は必要ない)\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  FOR, // [!code ++]\n  // .\n  // .\n  JS_FUNCTION_EXPRESSION, // [!code ++]\n}\n\nexport type ParentNode =\n  | RootNode\n  | ElementNode\n  | ForNode // [!code ++]\n  | IfBranchNode\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR\n  source: ExpressionNode\n  valueAlias: ExpressionNode | undefined\n  keyAlias: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  parseResult: ForParseResult // 後述\n  codegenNode?: ForCodegenNode\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true\n  tag: typeof FRAGMENT\n  props: undefined\n  children: ForRenderListExpression\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST // 後述\n  arguments: [ExpressionNode, ForIteratorExpression]\n}\n\n// renderList の第二引数でコールバック関数を使用するので、関数式にも対応します。\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n}\n\n// v-for の場合、 return は決まっているので、それ用のASTとして表現します。\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression // [!code ++]\n```\n\n`RENDER_LIST` に関しては，例の如く runtimeHelpers に追加しておきます．\n\n```ts\n// runtimeHelpers.ts\n// .\n// .\n// .\nexport const RENDER_LIST = Symbol() // [!code ++]\n\nexport const helperNameMap: Record<symbol, string> = {\n  // .\n  // .\n  [RENDER_LIST]: `renderList`, // [!code ++]\n  // .\n  // .\n}\n```\n\n`ForParseResult` についてですが，こちらの定義は transform/vFor にあります．\n\n```ts\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n```\n\nそれぞれが何を指しているかというと，\n\n`v-for=\"(fruit, i) in fruits\"` のような場合に\n\n- source: `fruits`\n- value: `fruit`\n- key: `i`\n- index: `undefined`\n\nのようになります．`index` は v-for にオブジェクトを適応した際に 3 つ目の引数として取られるものです．\n\nhttps://ja.vuejs.org/guide/essentials/list.html#v-for-with-an-object\n\n![ForParseResult shape](/figures/50-basic-template-compiler/v-for/for-parse-result.svg)\n\nvalue に関しては，`{ id, name, color, }` のように分割代入を使用した場合は複数の Identifier を持つことになります．\n\nこれら，value, key, index で定義された Identifier を収集し，prefix の付与をスキップしていきます．\n\n## codegen の実装\n\n少し順番が前後してしまいますが，codegen の方は大した話がないので先に実装を済ませてしまいます．  \nやることはたった二つ．`NodeTypes.FOR` のハンドリングと関数式の codegen です．(なんだかんだ関数式は初登場でした)\n\n```ts\nswitch (node.type) {\n  case NodeTypes.ELEMENT:\n  case NodeTypes.FOR: // [!code ++]\n  case NodeTypes.IF:\n  // .\n  // .\n  // .\n  case NodeTypes.JS_FUNCTION_EXPRESSION: // [!code ++]\n    genFunctionExpression(node, context, option) // [!code ++]\n    break // [!code ++]\n  // .\n  // .\n  // .\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context\n  const { params, returns, newline } = node\n\n  push(`(`, node)\n  if (isArray(params)) {\n    genNodeList(params, context, option)\n  } else if (params) {\n    genNode(params, context, option)\n  }\n  push(`) => `)\n  if (newline) {\n    push(`{`)\n    indent()\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `)\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option)\n    } else {\n      genNode(returns, context, option)\n    }\n  }\n  if (newline) {\n    deindent()\n    push(`}`)\n  }\n}\n```\n\n特に難しいところはないかと思います．これでおしまいです．\n\n## transformer の実装\n\n### 下準備\n\ntransformer の実装をしていきますが，ここでもまたいくつか下準備です．\n\nv-on の時にもやりましたが，v-for の場合には processExpression を実行するタイミングが少し特殊 (ローカル変数を収集しないといけない) なので，transformExpression の方ではスキップしてあげます．\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (\n        dir.type === NodeTypes.DIRECTIVE &&\n        dir.name !== 'for' // [!code ++]\n      ) {\n        // .\n        // .\n        // .\n      }\n    }\n  }\n}\n```\n\n### Identifier の収集\n\nさて，ここからはメインの実装をしていくわけですが，先にどのように identifier を収集していくかを考えていきましょう．\n\n今回は `fruit` のようなシンプルなものだけではなく，`{ id, name, color }` のような分割代入も考慮する必要があります．  \nついては，例の如く TreeWalker を使用する必要があるようです．\n\n現在 processExpression では identifier を探索し， `_ctx` を付与するような実装がされていますが，今回は付与せずに収集だけするような実装が必要そうです．これを実現していきます．\n\nまず，収集したものを溜めておくための場所を用意します．これは各 Node が持っておいた方が codegen などの時に便利なので，その Node 上に存在する identifier (複数) を持っておけるようなプロパティを AST に追加してしまいましょう．\n\n対象は `CompoundExpressionNode` と `SimpleExpressionNode` です．\n\n`fruit` のようなシンプルなものは `SimpleExpressionNode` に，  \n`{ id, name, color }` のような分割代入は `CompoundExpressionNode` になります．(イメージで言うと，`[\"{\", simpleExpr(\"id\"), \",\", simpleExpr(\"name\"), \",\", simpleExpr(\"color\"), \"}\"]` のような compound expr になる)\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[] // [!code ++]\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n  identifiers?: string[] // [!code ++]\n}\n```\n\nprocessExpression の方で，ここに identifier を収集していくような実装をし，収集した identifier を transformer の context に追加することによって prefix の付与をスキップしていきます．\n\n今，そこに identifier を追加/削除するための関数は，単一の識別子を string で受け取るような構成になってしまっているため，`{ identifier: string [] }` を想定した形に変更してあげます．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  addIdentifiers(exp: ExpressionNode | string): void\n  removeIdentifiers(exp: ExpressionNode | string): void\n  // .\n  // .\n  // .\n}\n\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        addId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(addId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        addId(exp.content)\n      }\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        removeId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(removeId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        removeId(exp.content)\n      }\n    }\n  },\n  // .\n  // .\n  // .\n}\n```\n\nそれでは，processExpression の方で identifier を収集する実装をやっていきます．\n\nprocessExpression の方で `asParams` というようなオプションを定義して，こちらが true になっている場合には prefix の付与をスキップして node.identifiers に identifier を収集するような実装をしていきます．\n\nasParams と言うのは，renderList の第二引数のコールバック関数に定義された引数(ローカル変数) のことを想定しているものです．\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false, // [!code ++]\n) {\n  // .\n  if (isSimpleIdentifier(rawExp)) {\n    const isScopeVarReference = ctx.identifiers[rawExp]\n    if (\n      !asParams && // [!code ++]\n      !isScopeVarReference\n    ) {\n      node.content = rewriteIdentifier(rawExp)\n    } // [!code ++]\n    return node\n\n    // .\n  }\n}\n```\n\nsimpleIdentifier の場合はこれでおしまいです．問題はそれ以外です．\n\nこちらは babelUtils に実装した `walkIdentifiers` を利用していきます．\n\n関数の引数として定義されたローカル変数を想定するので，こちらの方でも 「関数の引数」のように変換し，walkIdentifier の方でも Function の param として探索するようにします．\n\n```ts\n// asParams の場合は、関数の引数のように変換する\nconst source = `(${rawExp})${asParams ? `=>{}` : ``}`\n```\n\nwalkIdentifiers の方が多少複雑です．\n\n```ts\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  // .\n\n  ;(walk as any)(root, {\n    // prettier-ignore\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n        \n      } else if (isFunctionType(node)) { // [!code ++]\n        // 後述 (この関数の中で knownIds に identifier を収集している)\n        walkFunctionParams(node, (id) => // [!code ++]\n          markScopeIdentifier(node, id, knownIds)// [!code ++]\n        ); // [!code ++]\n      } // [!code ++]\n    },\n  })\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)\n}\n```\n\nやっていることとしては， node が関数だった場合には，その引数を walk し，identifiers に identifier を収集しているだけです．\n\n`walkIdentifiers` を呼び出す側では，`knownIds` を定義し，walkIdentifiers にこの `knownIds` を渡してあげ，収集させます．\n\n`walkIdentifiers` で収集した後で，最後，CompoundExpression を生成するタイミングで `knownIds` を元に identifiers を生成します．\n\n```ts\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers)\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // 渡す\n  parentStack,\n)\n\n// .\n// .\n// .\n\nret.identifiers = Object.keys(knownIds) //　knownIds を元に identifiers を生成\nreturn ret\n```\n\n少しファイルが前後しますが，walkFunctionParams, markScopeIdentifier は何をやっているかというと，これは単純で， param の walk と Node.name を knownIds に追加しているだけです．\n\n```ts\nexport function walkFunctionParams(\n  node: Function,\n  onIdent: (id: Identifier) => void,\n) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id)\n    }\n  }\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return\n  }\n  if (name in knownIds) {\n    knownIds[name]++\n  } else {\n    knownIds[name] = 1\n  }\n  ;(node.scopeIds || (node.scopeIds = new Set())).add(name)\n}\n```\n\nこれで identifier の収集ができるようになったはずです． これを使って transformFor を実装し，v-for ディレクティブを完成させましょう！\n\n### transformFor\n\nさて，山は超えたのであとはいつも通り今あるものを使って transformer を実装していきます．\nあと少し，頑張りましょう！\n\nこちらも v-if と同様，構造に関与するものなので createStructuralDirectiveTransform で実装していきます．\n\nおそらくこちらはコードベースで説明を書いて行った方がわかりやすいと思うので解説込みのコードを以下に記載しますが，ぜひこちらを見る前にソースコードを読んで自力で実装してみてください！\n\n```ts\n// こちらは v-if の時と同様、大枠の実装になります。\n// しかるべきところで processFor を実行し、しかるべきところで codegenNode を生成します。\n// processFor が一番複雑な実装になります。\nexport const transformFor = createStructuralDirectiveTransform(\n  'for',\n  (node, dir, context) => {\n    return processFor(node, dir, context, forNode => {\n      // 想定通り、renderList を呼び出すコードを生成します。\n      const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n        forNode.source,\n      ]) as ForRenderListExpression\n\n      // v-for のコンテナとなる Fragment の codegenNode を生成\n      forNode.codegenNode = createVNodeCall(\n        context,\n        context.helper(FRAGMENT),\n        undefined,\n        renderExp,\n      ) as ForCodegenNode\n\n      // codegen の process (processFor 内で、parse, identifiers の収集後に実行されます)\n      return () => {\n        const { children } = forNode\n        const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall\n\n        renderExp.arguments.push(\n          createFunctionExpression(\n            createForLoopParams(forNode.parseResult),\n            childBlock,\n            true /* force newline */,\n          ) as ForIteratorExpression,\n        )\n      }\n    })\n  },\n)\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  // v-for の式を解析します。\n  // parseResult の段階ですでに各 Node の identifiers は収集されています。\n  const parseResult = parseForExpression(\n    dir.exp as SimpleExpressionNode,\n    context,\n  )\n\n  const { addIdentifiers, removeIdentifiers } = context\n\n  const { source, value, key, index } = parseResult!\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  }\n\n  // Node を forNode に置き換える\n  context.replaceNode(forNode)\n\n  if (!context.isBrowser) {\n    // 収集された identifiers を context に追加して、\n    value && addIdentifiers(value)\n    key && addIdentifiers(key)\n    index && addIdentifiers(index)\n  }\n\n  // code を生成します。 (これにより、 ローカル変数の prefix の付与をスキップできる)\n  const onExit = processCodegen && processCodegen(forNode)\n\n  return () => {\n    value && removeIdentifiers(value)\n    key && removeIdentifiers(key)\n    index && removeIdentifiers(index)\n\n    if (onExit) onExit()\n  }\n}\n\n// 正規表現を活用して v-for に与えられた式を解析します。\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/\nconst stripParensRE = /^\\(|\\)$/g\n\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc\n  const exp = input.content\n  const inMatch = exp.match(forAliasRE)\n\n  if (!inMatch) return\n\n  const [, LHS, RHS] = inMatch\n  const result: ForParseResult = {\n    source: createAliasExpression(\n      loc,\n      RHS.trim(),\n      exp.indexOf(RHS, LHS.length),\n    ),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  }\n\n  if (!context.isBrowser) {\n    result.source = processExpression(\n      result.source as SimpleExpressionNode,\n      context,\n    )\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, '').trim()\n  const iteratorMatch = valueContent.match(forIteratorRE)\n  const trimmedOffset = LHS.indexOf(valueContent)\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, '').trim()\n    const keyContent = iteratorMatch[1].trim()\n    let keyOffset: number | undefined\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)\n      result.key = createAliasExpression(loc, keyContent, keyOffset)\n      if (!context.isBrowser) {\n        // ブラウザモードでない場合、asParams を true にし、key の identifiers を収集します。\n        result.key = processExpression(result.key, context, true)\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim()\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key\n              ? keyOffset! + keyContent.length\n              : trimmedOffset + valueContent.length,\n          ),\n        )\n        if (!context.isBrowser) {\n          // ブラウザモードでない場合、asParams を true にし、index の identifiers を収集します。\n          result.index = processExpression(result.index, context, true)\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset)\n    if (!context.isBrowser) {\n      // ブラウザモードでない場合、asParams を true にし、value の identifiers を収集します。\n      result.value = processExpression(result.value, context, true)\n    }\n  }\n\n  return result\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(\n    content,\n    false,\n    getInnerRange(range, offset, content.length),\n  )\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs])\n}\n\nfunction createParamsList(\n  args: (ExpressionNode | undefined)[],\n): ExpressionNode[] {\n  let i = args.length\n  while (i--) {\n    if (args[i]) break\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))\n}\n```\n\nさて，残りは実際にコンパイル後のコードに含まれる renderList の実装であったり，transformer の登録を実装できれば v-for が動くようになるはずです！\n\n実際に動かしてみましょう！\n\n![v-for result in the browser](/figures/50-basic-template-compiler/v-for/v-for-result.png)\n\n順調そうです．\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/050_v_for)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/070-resolve-component.md",
    "content": "# コンポーネントを解決する\n\n実は，まだ私たちの chibivue の template はコンポーネントを解決することができません．  \nここでそれを実装していくのですが，Vue.js ではコンポーネントの解決方法がいくつかあります．\n\nまずはいくつかの解決方法についておさらいしてみましょう．\n\n## コンポーネントの解決方法\n\n### 1. Components Option (ローカル登録)\n\nおそらく，これが最も単純なコンポーネントの解決方法です．\n\nhttps://vuejs.org/api/options-misc.html#components\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: {\n    MyComponent,\n    MyComponent2: MyComponent,\n  },\n}\n</script>\n\n<template>\n  <MyComponent />\n  <MyComponent2 />\n</template>\n```\n\ncomponents オプションに指定したオブジェクトの key 名が，テンプレート内で使用できるコンポーネント名になります．\n\n### 2. app に登録 (グローバル登録)\n\n作成した Vue アプリケーションの `.component()` メソッドを使うことでアプリケーション全体で使用できるコンポーネントを登録することができます．\n\nhttps://vuejs.org/guide/components/registration.html#global-registration\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({})\n\napp\n  .component('ComponentA', ComponentA)\n  .component('ComponentB', ComponentB)\n  .component('ComponentC', ComponentC)\n```\n\n### 3. 動的コンポーネント + is 属性\n\nis 属性を使うことで，動的にコンポーネントを切り替えることができます．\n\nhttps://vuejs.org/api/built-in-special-elements.html#component\n\n```vue\n<script>\nimport Foo from './Foo.vue'\nimport Bar from './Bar.vue'\n\nexport default {\n  components: { Foo, Bar },\n  data() {\n    return {\n      view: 'Foo',\n    }\n  },\n}\n</script>\n\n<template>\n  <component :is=\"view\" />\n</template>\n```\n\n### 4. script setup 時の import\n\nscript setup では，import したコンポーネントをそのまま使用することができます．\n\n```vue\n<script setup>\nimport MyComponent from './MyComponent.vue'\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n---\n\n他にも，非同期コンポーネントや組み込みコンポーネント, `component` タグなどもありますが，今回は上記 ２ つ (1, 2) に対応してみようと思います．\n\n3 に関しては，1, 2 が対応できれば拡張するだけです． 4 に関してはまだ script setup を実装していないので，少し後回しにします．\n\n## 基本アプローチ\n\nどのようにコンポーネントを解決していくかですが，基本的には以下のような流れになります．\n\n- どこかしらに，テンプレート内で使う名前とコンポーネントのレコードを保持する\n- ヘルパー関数を用いて，名前を元にコンポーネントを解決する\n\n1 の形も 2 の形も，登録する場所が少々異なるだけで，単に名前とコンポーネントのレコードを保持しているだけです．  \nレコードを保持していれば，必要になったところで名前からコンポーネントを解決することができるので，どちらも同じような実装になります．\n\n先に，想定されるコードと，コンパイル結果を見てみましょう．\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default defineComponent({\n  components: { MyComponent },\n})\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n```js\n// コンパイル結果\n\nfunction render(_ctx) {\n  const {\n    resolveComponent: _resolveComponent,\n    createVNode: _createVNode,\n    Fragment: _Fragment,\n  } = ChibiVue\n\n  const _component_MyComponent = _resolveComponent('MyComponent')\n\n  return _createVNode(_Fragment, null, _createVNode(_component_MyComponent))\n}\n```\n\nこのような感じです．\n\n## 実装\n\n### AST\n\nコンポーネントとして解決するコードを生成するためには，\"MyComponent\" がコンポーネントであることを知っている必要があります．  \nparse の段階で，タグ名をハンドリングして，AST 上は通常の Element と Component で分けるようにします．\n\nまずは AST の定義を考えてみましょう．  \nComponentNode は通常の Element と同じように， props や children を持ちます．  \nこれらの共通部分を `BaseElementNode` としてまとめつつ，これまでの `ElementNode` は `PlainElementNode` とし，  \n`ElementNode` は `PlainElementNode` と `ComponentNode` のユニオンにしてしまいます．\n\n```ts\n// compiler-core/ast.ts\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  tagType: ElementTypes\n  isSelfClosing: boolean\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT\n  codegenNode: VNodeCall | undefined\n}\n```\n\n内容としては今のところ変わりありませんが，tagType だけ区別して， ast は別物として扱います．  \n今後，これを使って transform の方で helper 関数の追加であったりを行っていきます．\n\n### Parser\n\nさて続いては，上記の AST を生成するためのパーサの実装です．  \n基本的には tag 名を判断して tagType を決めるだけです．\n\n問題は，どうやって Element なのか Component なのかを判断するかです．\n\n基本的な考え方は単純で，\"ネイティブなタグかどうか\" を判断するだけです．\n\n・  \n・  \n・\n\n「え，いやいや，だからそれをどうやって実装するかという話じゃないの ?」\n\nはい．ここは力技です．ネイティブなタグ名をあらかじめ列挙し，それにマッチするかどうかで判断します．  \n列挙するべき項目なんてものは，仕様をみに行けば全て書いてあるはずなので，それを信頼して使います．\n\nここで一つ，問題があるとすれば，「何がネイティブなタグかどうかは環境によって変わる」という点です．  \n今回でいえば，ブラウザです．何が言いたいのかというと，「compiler-core は環境依存であってはならない」ということです．  \n私たちはこれまで DOM に依存するような実装は compiler-dom に実装してきました．今回のこの列挙もその例外ではありません．\n\nそれに伴って，「ネイティブなタグ名であるかどうか」という関数をパーサのオプションとして外から注入できるような実装にします．\n\nこれからのことも考えて，オプションは色々後から追加しやすいようにしておきます．\n\n```ts\ntype OptionalOptions = 'isNativeTag' // | TODO: 今後増やしていく (かも)\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>\n\nexport interface ParserContext {\n  // .\n  // .\n  options: MergedParserOptions // [!code ++]\n  // .\n  // .\n}\n\nfunction createParserContext(\n  content: string,\n  rawOptions: ParserOptions, // [!code ++]\n): ParserContext {\n  const options = Object.assign({}, defaultParserOptions) // [!code ++]\n\n  let key: keyof ParserOptions // [!code ++]\n  // prettier-ignore\n  for (key in rawOptions) { // [!code ++]\n    options[key] = // [!code ++]\n      rawOptions[key] === undefined // [!code ++]\n        ? defaultParserOptions[key] // [!code ++]\n        : rawOptions[key]; // [!code ++]\n  } // [!code ++]\n\n  // .\n  // .\n  // .\n}\n\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {}, // [!code ++]\n): RootNode => {\n  const context = createParserContext(\n    content,\n    options, // [!code ++]\n  )\n  const children = parseChildren(context, [])\n  return createRoot(children)\n}\n```\n\nさてさて，そうしましたら， compiler-dom の方でネイティブなタグ名を列挙して，それをオプションとして渡してあげます．\n\ncompiler-dom と言いましたが，実は列挙自体は shared/domTagConfig.ts で行われています．\n\n```ts\nimport { makeMap } from './makeMap'\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +\n  'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +\n  'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +\n  'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +\n  'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +\n  'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +\n  'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +\n  'option,output,progress,select,textarea,details,dialog,menu,' +\n  'summary,template,blockquote,iframe,tfoot'\n\nexport const isHTMLTag = makeMap(HTML_TAGS)\n```\n\nなんとも禍々しいですね！！\n\nでもこれが正しい実装なのです．\n\nhttps://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/shared/src/domTagConfig.ts#L6\n\ncompiler-dom/parserOptions.ts を作成し，コンパイラに渡します．\n\n```ts\n// compiler-dom/parserOptions.ts\n\nimport { ParserOptions } from '../compiler-core'\nimport { isHTMLTag, isSVGTag } from '../shared/domTagConfig'\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),\n}\n```\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(\n    template,\n    Object.assign(\n      {},\n      parserOptions, // [!code ++]\n      defaultOption,\n      {\n        directiveTransforms: DOMDirectiveTransforms,\n      },\n    ),\n  )\n}\n```\n\n少し話が飛びましたが，パーサの実装に必要なものは揃ったので，残りの部分を実装していきます．\n\n残りはとっても簡単です．コンポーネント化どうかを判断して tagType を生やしてあげるだけです．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // .\n  // .\n  let tagType = ElementTypes.ELEMENT // [!code ++]\n  // prettier-ignore\n  if (isComponent(tag, context)) { // [!code ++]\n    tagType = ElementTypes.COMPONENT;// [!code ++]\n  } // [!code ++]\n\n  return {\n    // .\n    tagType, // [!code ++]\n    // .\n  }\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options\n  if (\n    // NOTE: Vue.js では、先頭が大文字のタグはコンポーネントとして扱われるようです。\n    // ref: https://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/compiler-core/src/parse.ts#L662\n    /^[A-Z]/.test(tag) ||\n    (options.isNativeTag && !options.isNativeTag(tag))\n  ) {\n    return true\n  }\n}\n```\n\nこれで parser と AST は OK です．これからはこれらを使って transform と codegen を実装していきます．\n\n### Transform\n\ntransform の方でやることはとても簡単です．\n\ntransformElement で，Node が ComponentNode だった場合に少々変換してあげるだけです．\n\nこの際，context にも component を登録しておいてあげます．  \nこれは，codegen の際にまとめて resolve してあげるためです．\n後述しますが，codegen の方ではコンポーネントは assets としてまとめて resolve されます．\n\n```ts\n// compiler-core/transforms/transformElement.ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    // .\n    // .\n\n    const isComponent = node.tagType === ElementTypes.COMPONENT // [!code ++]\n\n    const vnodeTag = isComponent // [!code ++]\n      ? resolveComponentType(node as ComponentNode, context) // [!code ++]\n      : `\"${tag}\"` // [!code ++]\n\n    // .\n    // .\n  }\n}\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node\n  context.helper(RESOLVE_COMPONENT)\n  context.components.add(tag) // 後述\n  return toValidAssetId(tag, `component`)\n}\n```\n\n```ts\n// util.ts\nexport function toValidAssetId(\n  name: string,\n  type: 'component', // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === '-' ? '_' : name.charCodeAt(replaceValue).toString()\n  })}`\n}\n```\n\ncontext の方にも登録できるようにしておきます．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  components: Set<string> // [!code ++]\n  // .\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  {\n    nodeTransforms = [],\n    directiveTransforms = {},\n    isBrowser = false,\n  }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    components: new Set(), // [!code ++]\n    // .\n  }\n}\n```\n\nそして，context にまとめられて components は登録対象のコンポーネントの RootNode に全て登録してあげます．\n\n```ts\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT\n  children: TemplateChildNode[]\n  codegenNode?: TemplateChildNode | VNodeCall\n  helpers: Set<symbol>\n  components: string[] // [!code ++]\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  createRootCodegen(root, context)\n  root.helpers = new Set([...context.helpers.keys()])\n  root.components = [...context.components] // [!code ++]\n}\n```\n\nこれで，あとは RootNode.components を codegen で使うだけです．\n\n### Codegen\n\n最初に見たコンパイル結果のように，ヘルパー関数に名前を渡して解決するコードを生成するだけです．  \n今後のためを考えて assets というふうな抽象化をしています．\n\n```ts\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  // .\n  // .\n  genFunctionPreamble(ast, context) // NOTE: 将来的には関数の外に出す\n\n  // prettier-ignore\n  if (ast.components.length) { // [!code ++]\n    genAssets(ast.components, \"component\", context); // [!code ++]\n    newline(); // [!code ++]\n    newline(); // [!code ++]\n  } // [!code ++]\n\n  push(`return `)\n  // .\n  // .\n}\n\nfunction genAssets(\n  assets: string[],\n  type: 'component' /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === 'component') {\n    const resolver = helper(RESOLVE_COMPONENT)\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i]\n\n      push(\n        `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(\n          id,\n        )})`,\n      )\n      if (i < assets.length - 1) {\n        newline()\n      }\n    }\n  }\n}\n```\n\n### runtime-core 側の実装\n\nここまでくれば目的のコードは生成できているので，あとは runtime-core の実装です．\n\n#### コンポーネントのオプションとして component を追加できるように\n\nこれは単純で，option に追加するだけです．\n\n```ts\nexport type ComponentOptions<\n  // .\n  // .\n> = {\n  // .\n  components?: Record<string, Component>\n  // .\n}\n```\n\n#### app のオプションとして components を追加できるように\n\nこちらも単純です．\n\n```ts\nexport interface AppContext {\n  // .\n  components: Record<string, Component> // [!code ++]\n  // .\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    // .\n    components: {}, // [!code ++]\n    // .\n  }\n}\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    // .\n    const app: App = (context.app = {\n      // .\n      // prettier-ignore\n      component(name: string, component: Component): any { // [!code ++]\n        context.components[name] = component; // [!code ++]\n        return app; // [!code ++]\n      },\n    })\n  }\n}\n```\n\n#### 上記二つからコンポーネントを解決する関数の実装\n\nこちらも特に説明することはないでしょう．  \nローカル/グローバルに登録されたコンポーネントをそれぞれに探索し，コンポーネントを返します．  \n見つからなかった場合は fallback としてそのまま名前を返します．\n\n```ts\n// runtime-core/helpers/componentAssets.ts\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance // 後述\n  if (instance) {\n    const Component = instance.type\n    const res =\n      // local registration\n      resolve((Component as ComponentOptions).components, name) ||\n      // global registration\n      resolve(instance.appContext.components, name)\n    return res\n  }\n\n  return name\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry &&\n    (registry[name] ||\n      registry[camelize(name)] ||\n      registry[capitalize(camelize(name))])\n  )\n}\n```\n\n一点，注意点があるのは `currentRenderingInstance` についてです．\n\nresolveComponent ではローカル登録されたコンポーネントを辿るために，現在レンダリングされているコンポーネントにアクセスする必要があります．  \n(レンダリング中のコンポーネントの components オプションを探索したいため)\n\nそれに伴って，`currentRenderingInstance` というものを用意し，レンダリングする際にこれを更新していく実装にしてみます．\n\n```ts\n// runtime-core/componentRenderContexts.ts\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance\n  currentRenderingInstance = instance\n  return prev\n}\n```\n\n```ts\n// runtime-core/renderer.ts\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const componentUpdateFn = () => {\n    // .\n    // .\n    const prev = setCurrentRenderingInstance(instance) // [!code ++]\n    const subTree = (instance.subTree = normalizeVNode(render(proxy!))) // [!code ++]\n    setCurrentRenderingInstance(prev) // [!code ++]\n    // .\n    // .\n  }\n  // .\n  // .\n}\n```\n\n## いざ動かしてみる\n\nお疲れ様でした．ここまででようやくコンポーネントを解決することができるようになりました．\n\n実際にプレイグラウンドの方で動かしてみましょう！\n\n```ts\nimport { createApp } from 'chibivue'\n\nimport App from './App.vue'\nimport Counter from './components/Counter.vue'\n\nconst app = createApp(App)\napp.component('GlobalCounter', Counter)\napp.mount('#app')\n```\n\nApp.vue\n\n```vue\n<script>\nimport Counter from './components/Counter.vue'\n\nimport { defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  components: { Counter },\n})\n</script>\n\n<template>\n  <Counter />\n  <Counter />\n  <GlobalCounter />\n</template>\n```\n\ncomponents/Counter.vue\n\n```vue\n<script>\nimport { ref, defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n})\n</script>\n\n<template>\n  <button @click=\"count++\">count: {{ count }}</button>\n</template>\n```\n\n![resolveComponent result in the browser](/figures/50-basic-template-compiler/resolve-component/resolve-components-result.png)\n\n正常に動作しているようです！やったね！\n\nここまでのソースコード: [GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/060_resolve_components)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/080-component-slot-outlet.md",
    "content": "# コンポーネントのスロット\n\n## 目指したい開発者インターフェース\n\nBasic Component System のスロットの実装で，すでにランタイムの実装はあります．\\\nしかし，私たちはまだ template でスロットを扱うことができていません．\n\n以下のような SFC を扱えるようにしたいです．\\\n(SFC とは言ってますが，実際には template のコンパイラの実装です．)\n\n```vue\n<!-- Comp.vue -->\n<template>\n  <p><slot name=\"default\" /></p>\n</template>\n```\n\n```vue\n<!-- App.vue -->\n<script>\nimport Comp from './Comp.vue'\nexport default {\n  components: {\n    Comp,\n  },\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n}\n</script>\n\n<template>\n  <Comp>\n    <template #default>\n      <button @click=\"count++\">count is: {{ count }}</button>\n    </template>\n  </Comp>\n</template>\n```\n\nVue.js のスロットには，いくつかの種類があります．\n\n- デフォルトスロット\n- 名前付きスロット\n- スコープ付きスロット\n\nしかし，すでにランタイムの実装を皆さんならわかると思いますが，これらは全てただの callback 関数です．\\\n念の為おさらいしておきましょう．\n\n上記のようなコンポーネントは，以下のような render 関数に変換されます．\n\n```js\nh(Comp, null, {\n  default: () =>\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n})\n\n```\n\ntemplate 上では `name=\"default\"` を省略することが可能ですが，依然としてそうであってもランタイム上は `default` という名前のスロットになります．\\\nこの実装は名前付きスロットのコンパイラの実装が終わった後で，デフォルトスロットのコンパイラの実装を行うことにしましょう．\n\n## コンパイラを実装していく (スロットの定義)\n\n例の如く，parse と codegen の処理を実装していくのですが，今回はスロットの定義の方と，スロットの挿入の方の処理を実装していきます．\n\nまずはスロットの定義の方です．\\\n子コンポーネント側で `<slot name=\"my-slot\"/>` として表現される部分のコンパイルです．\n\nランタイムの方では，`renderSlot` というヘルパー関数を用意し，コンポーネントのインスタンスを通じて (`ctx.$slot` を通じて) 挿入されるスロットと，その名前を引数に渡してあげるような形にします．\\\nソースコード的には概ね以下のような形にコンパイルします．\n\n```js\n_renderSlot(_ctx.$slots, \"my-slot\")\n```\n\nslot の定義は AST 上は `SlotOutletNode` というノードで表現することにします．\\\n`ast.ts` に以下のような定義を追加します．\n\n```ts\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT, // [!code ++]\n}\n\n// ...\n\nexport type ElementNode = \n  | PlainElementNode \n  | ComponentNode \n  | SlotOutletNode // [!code ++]\n\n// ...\n\nexport interface SlotOutletNode extends BaseElementNode { // [!code ++]\n  tagType: ElementTypes.SLOT // [!code ++]\n  codegenNode: RenderSlotCall | undefined // [!code ++]\n} // [!code ++]\n\nexport interface RenderSlotCall extends CallExpression { // [!code ++]\n  callee: typeof RENDER_SLOT // [!code ++]\n  // $slots, name // [!code ++]\n  arguments: [string, string | ExpressionNode] // [!code ++]\n} // [!code ++]\n```\n\nそれでは，この AST を生成するために parse の処理を書いていきましょう．\n\n`parse.ts` です．やることは簡単で，tag をパースする際に `\"slot\"` であれば `ElementTypes.SLOT` に変更するだけです．\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // ...\n  let tagType = ElementTypes.ELEMENT\n  if (tag === 'slot') { // [!code ++]\n    tagType = ElementTypes.SLOT // [!code ++]\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT\n  }\n}\n```\n\nここまでできたら次は transformer を実装して codegenNode を生成していきます．\\\nヘルパー関数の `JS_CALL_EXPRESSION` にしてあげれば OK です．\n\n下準備として，`runtimeHelper.ts` に `RENDER_SLOT` を追加しておきます．\n\n```ts\n// ...\nexport const RENDER_LIST = Symbol()\nexport const RENDER_SLOT = Symbol() // [!code ++]\nexport const MERGE_PROPS = Symbol()\n// ...\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: 'renderSlot', // [!code ++]\n  [MERGE_PROPS]: 'mergeProps',\n  // ...\n}\n```\n\n新たに `transformSlotOutlet` という transformer を実装します．\\\nやることはとても簡単で，`ElementType.SLOT` である時に `node.props` から `name` を探しつつ，`RENDER_SLOT` の `JS_CALL_EXPRESSION` を生成してあげます．\\\n`:name=\"slotName\"` のようにバインディングであった場合も考慮してあげます．\n\nシンプルなので，transformer の全貌を以下に貼っておきます．(読んでみてください．)\n\n```ts\nimport { camelize } from '../../shared'\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from '../ast'\nimport { RENDER_SLOT } from '../runtimeHelpers'\nimport type { NodeTransform, TransformContext } from '../transform'\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node\n    const { slotName } = processSlotOutlet(node, context)\n    const slotArgs: CallExpression['arguments'] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ]\n\n    node.codegenNode = createCallExpression(\n      context.helper(RENDER_SLOT),\n      slotArgs,\n      loc,\n    )\n  }\n}\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`\n\n  const nonNameProps = []\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === 'name') {\n          slotName = JSON.stringify(p.value.content)\n        } else {\n          p.name = camelize(p.name)\n          nonNameProps.push(p)\n        }\n      }\n    } else {\n      if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {\n        if (p.exp) slotName = p.exp\n      } else {\n        if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content)\n        }\n        nonNameProps.push(p)\n      }\n    }\n  }\n\n  return { slotName }\n}\n```\n\nゆくゆくはここにスコープ付きスロットのための props 探索等も追加していくことになるでしょう．\n\n一点，注意点としては，`<slot />` という要素は transformElement の方でも引っかかってしまうので，`ElementTypes.SLOT` である時はそちらはスキップするように実装を追加しておきます．\n\n`transformElement.ts` です．\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if ( // [!code ++]\n      !( // [!code ++]\n        node.type === NodeTypes.ELEMENT && // [!code ++]\n        (node.tagType === ElementTypes.ELEMENT || // [!code ++]\n          node.tagType === ElementTypes.COMPONENT) // [!code ++]\n      ) // [!code ++]\n    ) { // [!code ++]\n      return // [!code ++]\n    } // [!code ++]\n\n    // ...\n  }\n}\n```\n\nあとは `compile.ts` で `transformSlotOutlet` を登録してあげればコンパイルはできるようになるはずです．\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformIf,\n      transformFor,\n      transformExpression,\n      transformSlotOutlet, // [!code ++]\n      transformElement,\n    ],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\nまだ，`renderSlot` というランタイムの関数は実装できていないので，そちらを最後に行ってスロットの定義の実装は終わりです．\n\n`packages/runtime-core/helpers/renderSlot.ts` を実装します．\n\n```ts\nimport { Fragment, type VNode, createVNode } from '../vnode'\nimport type { Slots } from '../componentSlots'\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name]\n  if (!slot) {\n    slot = () => []\n  }\n\n  return createVNode(Fragment, {}, slot())\n}\n```\n\nここまででスロットの定義の実装は終わりです．\\\n次はスロットの挿入側のコンパイラを実装していきましょう！\n\nここまでのソースコード:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/080_component_slot_outlet)"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/085-component-slot-insert.md",
    "content": "# スロットに対応する (利用編)\n\n## スロットの挿入\n\n続いてスロットの挿入側の実装です．\\\nこちらは親コンポーネント側で `<template #slot-name>` として表現される部分のコンパイルです．\n\n冒頭で説明した通り，スロットは以下のようにコンパイルされます．\n\n```js\nh(Comp, null, {\n  default: _withCtx(() => [\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n  ]),\n})\n```\n\nつまり，コンポーネントの子要素は `SlotsExpression` (ObjectExpression) として扱われ，各スロットは `FunctionExpression` として生成され，`withCtx` でラップされます．\n\n## withCtx の役割\n\n`withCtx` はスロット関数を正しいコンポーネントインスタンスのコンテキストで実行するためのヘルパー関数です．これにより，スロット内のリアクティブな依存関係が正しいコンポーネントに追跡されます．\n\n```ts\nexport function withCtx(\n  fn: Function,\n  ctx: ComponentInternalInstance | null = currentRenderingInstance,\n) {\n  if (!ctx) return fn;\n\n  const renderFnWithContext = (...args: any[]) => {\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    try {\n      return fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n    }\n  };\n\n  return renderFnWithContext;\n}\n```\n\n## AST の更新\n\nまずは AST の定義を更新します．\\\n`SlotsExpression` という型を追加し，スロット関数であることを示すために `FunctionExpression` に `isSlot` フラグを追加します．\n\n```ts\n// SlotsExpression is an ObjectExpression that represents the slots object\n// passed to a component. e.g., { default: () => [...], header: () => [...] }\nexport interface SlotsExpression extends ObjectExpression {}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n  isSlot?: boolean // [!code ++]\n}\n```\n\nまた，`VNodeCall` の `children` 型に `SlotsExpression` を追加します．\n\n```ts\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[]\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | SlotsExpression // [!code ++]\n    | undefined\n}\n```\n\n## ヘルパーの追加\n\n`runtimeHelpers.ts` に `WITH_CTX` を追加します．\n\n```ts\nexport const WITH_CTX = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_CTX]: 'withCtx',\n}\n```\n\n## ユーティリティ関数の追加\n\n`utils.ts` に `findDir` と `isTemplateNode` というユーティリティ関数を追加します．\n\n```ts\nexport function isTemplateNode(\n  node: RootNode | TemplateChildNode,\n): node is PlainElementNode & { tag: 'template' } {\n  return (\n    node.type === NodeTypes.ELEMENT &&\n    node.tagType === ElementTypes.ELEMENT &&\n    node.tag === 'template'\n  )\n}\n\nexport function findDir(\n  node: ElementNode,\n  name: string | RegExp,\n  allowEmpty: boolean = false,\n): DirectiveNode | undefined {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (\n      p.type === NodeTypes.DIRECTIVE &&\n      (allowEmpty || p.exp) &&\n      (typeof name === 'string' ? p.name === name : name.test(p.name))\n    ) {\n      return p\n    }\n  }\n}\n```\n\n`isTemplateNode` は `<template>` タグであるかを判定し，`findDir` は指定した名前のディレクティブを探します．\n\n## buildSlots の実装\n\nスロットの挿入を処理する `buildSlots` 関数を `transforms/vSlot.ts` に実装します．\n\n```ts\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type Property,\n  type SlotsExpression,\n  type TemplateChildNode,\n  createCallExpression,\n  createFunctionExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from '../ast'\nimport { WITH_CTX } from '../runtimeHelpers'\nimport type { TransformContext } from '../transform'\nimport { findDir, isStaticExp, isTemplateNode } from '../utils'\n\n// Build slots object for a component\nexport function buildSlots(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  slots: SlotsExpression\n} {\n  const { children } = node\n  const slotsProperties: Property[] = []\n\n  // 1. Check for slot with slotProps on component itself.\n  //    <Comp v-slot=\"{ prop }\"/>\n  const onComponentSlot = findDir(node, 'slot', true)\n  if (onComponentSlot) {\n    const { arg, exp } = onComponentSlot\n    slotsProperties.push(\n      createObjectProperty(\n        arg || createSimpleExpression('default', true),\n        buildSlotFn(exp, children, node.loc, context),\n      ),\n    )\n  }\n\n  // 2. Iterate through children and check for template slots\n  //    <template v-slot:foo=\"{ prop }\">\n  let hasTemplateSlots = false\n  const implicitDefaultChildren: TemplateChildNode[] = []\n\n  for (let i = 0; i < children.length; i++) {\n    const slotElement = children[i]\n    let slotDir: DirectiveNode | undefined\n\n    if (\n      !isTemplateNode(slotElement) ||\n      !(slotDir = findDir(slotElement, 'slot', true))\n    ) {\n      // not a <template v-slot>, skip.\n      if (slotElement.type !== NodeTypes.COMMENT) {\n        implicitDefaultChildren.push(slotElement)\n      }\n      continue\n    }\n\n    hasTemplateSlots = true\n    const { children: slotChildren, loc: slotLoc } = slotElement\n    const {\n      arg: slotName = createSimpleExpression(`default`, true),\n      exp: slotProps,\n    } = slotDir\n\n    const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc, context)\n    slotsProperties.push(createObjectProperty(slotName, slotFunction))\n  }\n\n  if (!onComponentSlot) {\n    if (!hasTemplateSlots) {\n      // implicit default slot (on component)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, children, node.loc, context),\n        ),\n      )\n    } else if (implicitDefaultChildren.length) {\n      // implicit default slot (mixed with named slots)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, implicitDefaultChildren, node.loc, context),\n        ),\n      )\n    }\n  }\n\n  const slots = createObjectExpression(\n    slotsProperties,\n    node.loc,\n  ) as SlotsExpression\n\n  return {\n    slots,\n  }\n}\n\nfunction buildSlotFn(\n  props: ExpressionNode | undefined,\n  children: TemplateChildNode[],\n  loc: any,\n  context: TransformContext,\n) {\n  const fn = createFunctionExpression(\n    props,\n    children,\n    false /* newline */,\n    children.length ? children[0].loc : loc,\n  )\n  fn.isSlot = true\n  return createCallExpression(context.helper(WITH_CTX), [fn], loc)\n}\n```\n\n`buildSlots` 関数は以下の 3 つのパターンを処理します:\n\n1. **コンポーネント自体に v-slot がある場合** (`<Comp v-slot=\"{ prop }\"/>`)\n2. **template タグで名前付きスロットを定義する場合** (`<template #foo>`)\n3. **暗黙のデフォルトスロット** (名前付きスロットがない場合の子要素)\n\n## transformElement の更新\n\n最後に `transformElement.ts` を更新して，コンポーネントの子要素を `buildSlots` で処理するようにします．\n\n```ts\nimport { buildSlots } from './vSlot'\n\n// ...\n\n// children\nif (node.children.length > 0) {\n  if (isComponent) {\n    // For components, build slots object // [!code ++]\n    const { slots } = buildSlots(node, context) // [!code ++]\n    vnodeChildren = slots as SlotsExpression // [!code ++]\n  } else if (node.children.length === 1) {\n    const child = node.children[0]\n    const type = child.type\n    const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n    if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n      vnodeChildren = child as TemplateTextChildNode\n    } else {\n      vnodeChildren = node.children\n    }\n  } else {\n    vnodeChildren = node.children\n  }\n}\n```\n\nこれでスロットの挿入側のコンパイルが完了です．\\\nコンポーネントの子要素は自動的にスロットオブジェクトに変換され，以下のようなコードが生成されます．\n\n```vue\n<Comp>\n  <template #header>\n    <h1>Header</h1>\n  </template>\n  <template #default>\n    <p>Content</p>\n  </template>\n</Comp>\n```\n\n↓\n\n```js\n_createVNode(_component_Comp, null, {\n  header: _withCtx(() => [_createVNode('h1', null, 'Header')]),\n  default: _withCtx(() => [_createVNode('p', null, 'Content')]),\n})\n```\n\nこれで基本的なスロットのコンパイラ実装は完了です！\n\nここまでのソースコード:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/085_component_slot_insert)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/090-other-directives.md",
    "content": "# その他のディレクティブ\n\nここまでで v-bind, v-on, v-if, v-for, v-model といった主要なディレクティブを実装してきました．\\\nこのチャプターでは，残りのビルトインディレクティブを実装していきます．\n\n実装するディレクティブは以下の通りです．\n\n- v-text\n- v-html\n- v-cloak\n- v-pre\n\nv-show については，ランタイムディレクティブの仕組みが必要になるため，カスタムディレクティブの章で扱います．\\\nまた，v-once と v-memo については最適化に関連する内容なので，Web Application Essentials の Optimizations の章で扱う予定です．\n\n## v-text\n\n### 目指したい開発者インターフェース\n\nv-text は要素の textContent を更新するディレクティブです．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello!')\n    return { msg }\n  },\n}\n</script>\n\n<template>\n  <span v-text=\"msg\"></span>\n  <!-- 以下と同等 -->\n  <span>{{ msg }}</span>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-text\n\n### 実装方針\n\nv-text の実装はとてもシンプルです．\\\nコンパイル時に v-text ディレクティブを `textContent` プロパティへのバインディングに変換するだけです．\n\n```html\n<span v-text=\"msg\"></span>\n```\n\n↓\n\n```ts\nh('span', { textContent: msg })\n```\n\n### compiler-dom に transformer を実装\n\nv-text は DOM 固有のディレクティブなので，compiler-dom に実装します．\n\n`packages/compiler-dom/src/transforms/vText.ts` を作成します．\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-text is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-text will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\nポイントは以下の通りです．\n\n- exp が存在しない場合はエラーを出力\n- 子要素が存在する場合は警告を出力し，子要素をクリア（v-text は子要素を上書きするため）\n- `textContent` プロパティとして exp をバインド\n\nあとは `packages/compiler-dom/src/index.ts` で transformer を登録します．\n\n```ts\nimport { transformVText } from './transforms/vText'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText, // [!code ++]\n}\n```\n\nこれで v-text の実装は完了です！\n\n## v-html\n\n### 目指したい開発者インターフェース\n\nv-html は要素の innerHTML を更新するディレクティブです．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const rawHtml = ref('<span style=\"color: red\">This should be red.</span>')\n    return { rawHtml }\n  },\n}\n</script>\n\n<template>\n  <p>Using v-html directive: <span v-html=\"rawHtml\"></span></p>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-html\n\n::: warning\nv-html は innerHTML を直接操作するため，XSS 脆弱性の原因になる可能性があります．\\\n信頼できないユーザー入力を v-html で表示することは避けてください．\n:::\n\n### 実装方針\n\nv-html も v-text と同様に，コンパイル時に `innerHTML` プロパティへのバインディングに変換します．\n\n```html\n<span v-html=\"rawHtml\"></span>\n```\n\n↓\n\n```ts\nh('span', { innerHTML: rawHtml })\n```\n\n### compiler-dom に transformer を実装\n\n`packages/compiler-dom/src/transforms/vHtml.ts` を作成します．\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-html is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-html will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\nv-text とほぼ同じ構造ですね．違いは `textContent` の代わりに `innerHTML` を使うことだけです．\n\n`packages/compiler-dom/src/index.ts` で transformer を登録します．\n\n```ts\nimport { transformVHtml } from './transforms/vHtml'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText,\n  html: transformVHtml, // [!code ++]\n}\n```\n\nこれで v-html の実装も完了です！\n\n## v-cloak\n\n### 目指したい開発者インターフェース\n\nv-cloak はコンポーネントがマウントされるまで要素を隠すためのディレクティブです．\\\nCSS と組み合わせて使用し，コンパイル前のテンプレート構文（マスタッシュなど）がユーザーに見えてしまうのを防ぎます．\n\n```css\n[v-cloak] {\n  display: none;\n}\n```\n\n```text\n<div v-cloak>\n  ｛｛ message ｝｝\n</div>\n```\n\nマウント後，v-cloak 属性は自動的に削除されます．\n\nhttps://vuejs.org/api/built-in-directives.html#v-cloak\n\n### 実装方針\n\nv-cloak の実装は非常にシンプルです．\\\nマウント時に v-cloak 属性を要素から削除するだけです．\n\nこれはコンパイラではなく，ランタイム側で処理します．\\\n具体的には，`renderer.ts` の `mountElement` 内で処理を追加します．\n\n### ランタイムに実装\n\n`packages/runtime-core/src/renderer.ts` の `mountElement` 関数に以下の処理を追加します．\n\n```ts\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  let el: RendererElement\n  const { type, props, children, shapeFlag } = vnode\n\n  el = vnode.el = hostCreateElement(type as string)\n\n  // ... 既存の処理 ...\n\n  // v-cloak の削除 // [!code ++]\n  if (props && 'v-cloak' in props) { // [!code ++]\n    delete (el as any)['v-cloak'] // [!code ++]\n    hostRemoveAttribute(el, 'v-cloak') // [!code ++]\n  } // [!code ++]\n\n  hostInsert(el, container, anchor)\n\n  // ... 既存の処理 ...\n}\n```\n\n`hostRemoveAttribute` は既存の `hostPatchProp` を利用して実装することもできますが，シンプルに `nodeOps` に追加しましょう．\n\n`packages/runtime-dom/src/nodeOps.ts` に追加します．\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // ... 既存の処理 ...\n  removeAttribute: (el, key) => {\n    el.removeAttribute(key)\n  },\n}\n```\n\n`packages/runtime-core/src/renderer.ts` の `RendererOptions` 型にも追加します．\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement,\n> {\n  // ... 既存の処理 ...\n  removeAttribute(el: HostElement, key: string): void\n}\n```\n\nこれで v-cloak の実装は完了です！\n\n## v-pre\n\n### 目指したい開発者インターフェース\n\nv-pre はこの要素とすべての子要素のコンパイルをスキップするためのディレクティブです．\\\nマスタッシュ構文などをそのまま表示したい場合に使用します．\n\n```text\n<template>\n  <span v-pre>｛｛ this will not be compiled ｝｝</span>\n</template>\n```\n\n上記のテンプレートは `｛｛ this will not be compiled ｝｝` というテキストをそのまま表示します．\n\nhttps://vuejs.org/api/built-in-directives.html#v-pre\n\n### 実装方針\n\nv-pre は他のディレクティブとは異なり，パーサーの段階で処理を行います．\\\nv-pre 属性を持つ要素を検出したら，その要素とその子要素に対してはディレクティブやマスタッシュ構文の解析をスキップします．\n\n### パーサーに実装\n\n`packages/compiler-core/src/parse.ts` に v-pre の処理を追加します．\n\nまず，パーサーコンテキストに `inVPre` フラグを追加します．\n\n```ts\nexport interface ParserContext {\n  // ... 既存のプロパティ ...\n  inVPre: boolean // [!code ++]\n}\n\nfunction createParserContext(content: string, options: ParserOptions): ParserContext {\n  return {\n    // ... 既存の処理 ...\n    inVPre: false, // [!code ++]\n  }\n}\n```\n\n次に，要素をパースする際に v-pre 属性をチェックし，その場合は `inVPre` を true にします．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // Start tag\n  const element = parseTag(context, TagType.Start)\n\n  // v-pre のチェック // [!code ++]\n  const isPreBoundary = element.props.some( // [!code ++]\n    p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre' // [!code ++]\n  ) // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = true // [!code ++]\n  } // [!code ++]\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = children\n\n    // End tag\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End)\n    }\n  }\n\n  // v-pre の終了 // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = false // [!code ++]\n  } // [!code ++]\n\n  return element\n}\n```\n\nそして，`inVPre` が true の場合は，ディレクティブやマスタッシュ構文の解析をスキップするようにします．\n\n`parseAttribute` 関数を修正します．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // ... 属性名のパース処理 ...\n\n  // v-pre の場合はディレクティブとして解析しない // [!code ++]\n  if (context.inVPre) { // [!code ++]\n    return { // [!code ++]\n      type: NodeTypes.ATTRIBUTE, // [!code ++]\n      name, // [!code ++]\n      value: value && { // [!code ++]\n        type: NodeTypes.TEXT, // [!code ++]\n        content: value.content, // [!code ++]\n        loc: value.loc, // [!code ++]\n      }, // [!code ++]\n      loc, // [!code ++]\n    } // [!code ++]\n  } // [!code ++]\n\n  // ディレクティブのパース処理 ...\n}\n```\n\nまた，`parseChildren` 関数でマスタッシュ構文の解析をスキップするようにします．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (startsWith(s, context.options.delimiters[0])) {\n      // v-pre の場合はマスタッシュをスキップ // [!code ++]\n      if (!context.inVPre) { // [!code ++]\n        node = parseInterpolation(context)\n      } // [!code ++]\n    } else if (s[0] === '<') {\n      // ... 要素のパース処理 ...\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\nこれで v-pre の実装は完了です！\n\n## 動作確認\n\nそれでは実装したディレクティブの動作を確認してみましょう．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>｛｛ msg ｝｝ will not be compiled</span>\n  </div>\n</template>\n```\n\nうまく動作しましたか？\\\nこれで基本的なビルトインディレクティブの実装が完了しました！\n\nv-show とカスタムディレクティブについては，次のチャプターで扱います．\\\nv-once と v-memo については，最適化の章で扱う予定です．\n\nここまでのソースコード:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/090_other_directives)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/100-chore-compiler.md",
    "content": "# コンパイラの細かい調整\n\nこのチャプターでは，テンプレートコンパイラの品質を向上させるためのいくつかの調整を行います．\\\n主に以下の 2 つのトピックを扱います．\n\n1. **ホワイトスペースの処理** - 不要な空白を削除・圧縮する\n2. **テキストノードの結合** - 隣接するテキストノードを効率的に結合する\n\nこれらは見た目の機能というよりは，生成されるコードの品質を向上させるための最適化です．\n\n## ホワイトスペースの処理\n\n### 問題点\n\n現在の実装では，テンプレート内のすべての空白がそのまま保持されます．\\\n例えば以下のようなテンプレートを考えてみましょう．\n\n```html\n<div>\n  <span>Hello</span>\n  <span>World</span>\n</div>\n```\n\n現在の実装では，`<div>` と `<span>` の間の改行やインデントもテキストノードとして保持されます．\\\nこれは無駄なノードを生成し，パフォーマンスに影響を与える可能性があります．\n\n### Vue.js のアプローチ\n\nVue.js では，`whitespace` オプションを使用してホワイトスペースの処理方法を制御できます．\n\n```ts\ntype WhitespaceStrategy = 'preserve' | 'condense'\n```\n\n- **`'condense'`** (デフォルト): 連続したホワイトスペースを圧縮し，不要なホワイトスペースを削除\n- **`'preserve'`**: ホワイトスペースをそのまま保持\n\n### condense モードの動作\n\ncondense モードでは，以下のルールに従ってホワイトスペースが処理されます．\n\n1. **先頭・末尾のホワイトスペースのみのテキストノード** → 削除\n2. **要素間の改行を含むホワイトスペース** → 削除\n3. **連続するホワイトスペース** → 単一のスペースに圧縮\n4. **要素間の改行を含まないホワイトスペース** → 保持（単一スペースに圧縮）\n\n例:\n\n```html\n<div>   <span/>    </div>\n<!-- 結果: <span/> のみが子ノード（前後のスペースは削除） -->\n\n<div/>\n<div/>\n<div/>\n<!-- 結果: 3つの div 要素のみ（改行を含むホワイトスペースは削除） -->\n\n<span>foo</span>  <span>bar</span>\n<!-- 結果: 要素間のスペースは保持される（改行がないため） -->\n```\n\n### 実装\n\nまず，`ParserOptions` に `whitespace` オプションを追加します．\n\n`packages/compiler-core/src/options.ts`:\n\n```ts\nexport interface ParserOptions {\n  // ... 既存のオプション ...\n  whitespace?: 'preserve' | 'condense' // [!code ++]\n}\n```\n\n`packages/compiler-core/src/parse.ts` にホワイトスペース処理の関数を追加します．\n\n```ts\nfunction isAllWhitespace(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (\n      c !== 0x20 && // space\n      c !== 0x09 && // tab\n      c !== 0x0a && // newline\n      c !== 0x0c && // form feed\n      c !== 0x0d    // carriage return\n    ) {\n      return false\n    }\n  }\n  return true\n}\n\nfunction hasNewlineChar(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (c === 0x0a || c === 0x0d) {\n      return true\n    }\n  }\n  return false\n}\n\nfunction condense(content: string): string {\n  let result = ''\n  let prevIsWhitespace = false\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    const isWhitespace =\n      c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d\n    if (isWhitespace) {\n      if (!prevIsWhitespace) {\n        result += ' '\n        prevIsWhitespace = true\n      }\n    } else {\n      result += content[i]\n      prevIsWhitespace = false\n    }\n  }\n  return result\n}\n\nfunction condenseWhitespace(\n  nodes: TemplateChildNode[],\n  context: ParserContext,\n): TemplateChildNode[] {\n  const shouldCondense = context.options.whitespace !== 'preserve'\n  let removedWhitespace = false\n\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i]\n    if (node.type === NodeTypes.TEXT) {\n      if (!context.inPre) {\n        if (isAllWhitespace(node.content)) {\n          const prev = nodes[i - 1]?.type\n          const next = nodes[i + 1]?.type\n          // 以下の場合に削除:\n          // - 先頭または末尾のホワイトスペース\n          // - (condense モード) コメント間のホワイトスペース\n          // - (condense モード) コメントと要素間のホワイトスペース\n          // - (condense モード) 改行を含む要素間のホワイトスペース\n          if (\n            !prev ||\n            !next ||\n            (shouldCondense &&\n              ((prev === NodeTypes.COMMENT &&\n                (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||\n                (prev === NodeTypes.ELEMENT &&\n                  (next === NodeTypes.COMMENT ||\n                    (next === NodeTypes.ELEMENT &&\n                      hasNewlineChar(node.content))))))\n          ) {\n            removedWhitespace = true\n            nodes[i] = null as any\n          } else {\n            // それ以外の場合は単一スペースに圧縮\n            node.content = ' '\n          }\n        } else if (shouldCondense) {\n          // condense モードでは連続するホワイトスペースを圧縮\n          node.content = condense(node.content)\n        }\n      }\n    }\n  }\n\n  return removedWhitespace ? nodes.filter(Boolean) : nodes\n}\n```\n\nそして，要素のパース時にこの関数を呼び出します．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // ... 既存の処理 ...\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = condenseWhitespace(children, context) // [!code ++]\n    // element.children = children // [!code --]\n\n    // ...\n  }\n\n  return element\n}\n```\n\nまた，ルートノードに対しても同様の処理を行います．\n\n```ts\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {},\n): RootNode => {\n  const context = createParserContext(content, options)\n  const children = parseChildren(context, [])\n  return createRoot(condenseWhitespace(children, context)) // [!code ++]\n  // return createRoot(children) // [!code --]\n}\n```\n\n## テキストノードの結合 (transformText)\n\n### 問題点\n\n現在の実装では，テキストノードとマスタッシュ構文（`{{ }}`）が別々のノードとして扱われます．\n\n```html\n<div>abc {{ d }} {{ e }}</div>\n```\n\nこのテンプレートは以下の 3 つの子ノードを持ちます:\n- `TEXT`: \"abc \"\n- `INTERPOLATION`: d\n- `TEXT`: \" \"\n- `INTERPOLATION`: e\n\nコード生成時にこれらを個別に処理すると，効率が悪くなります．\n\n### Vue.js のアプローチ\n\nVue.js では `transformText` というトランスフォーマーを使用して，隣接するテキストノードとマスタッシュ構文を 1 つの `CompoundExpression` に結合します．\n\n結合後:\n```ts\n// \"abc \" + d + \" \" + e\ncreateCompoundExpression(['abc ', d, ' ', e])\n```\n\nこれにより，コード生成時に効率的な連結演算として出力できます．\n\n### 実装\n\n`packages/compiler-core/src/transforms/transformText.ts` を作成します．\n\n```ts\nimport type { NodeTransform } from '../transform'\nimport {\n  type CompoundExpressionNode,\n  ElementTypes,\n  NodeTypes,\n  createCallExpression,\n  createCompoundExpression,\n} from '../ast'\nimport { isText } from '../utils'\nimport { CREATE_TEXT } from '../runtimeHelpers'\nimport { PatchFlags } from '@chibivue/shared'\n\n// 隣接するテキストノードとマスタッシュを1つの式に結合\n// 例: <div>abc {{ d }} {{ e }}</div> は1つの子ノードを持つ\nexport const transformText: NodeTransform = (node, context) => {\n  if (\n    node.type === NodeTypes.ROOT ||\n    node.type === NodeTypes.ELEMENT ||\n    node.type === NodeTypes.FOR ||\n    node.type === NodeTypes.IF_BRANCH\n  ) {\n    // 子の処理が完了した後に実行\n    return () => {\n      const children = node.children\n      let currentContainer: CompoundExpressionNode | undefined = undefined\n      let hasText = false\n\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child)) {\n          hasText = true\n          for (let j = i + 1; j < children.length; j++) {\n            const next = children[j]\n            if (isText(next)) {\n              if (!currentContainer) {\n                currentContainer = children[i] = createCompoundExpression(\n                  [child],\n                  child.loc,\n                )\n              }\n              // 隣接するテキストノードを結合\n              currentContainer.children.push(` + `, next)\n              children.splice(j, 1)\n              j--\n            } else {\n              currentContainer = undefined\n              break\n            }\n          }\n        }\n      }\n\n      if (\n        !hasText ||\n        // 単一のテキスト子を持つプレーン要素はそのまま残す\n        // ランタイムが textContent を直接設定する最適化パスを持つため\n        (children.length === 1 &&\n          (node.type === NodeTypes.ROOT ||\n            (node.type === NodeTypes.ELEMENT &&\n              node.tagType === ElementTypes.ELEMENT &&\n              !node.props.find(\n                p =>\n                  p.type === NodeTypes.DIRECTIVE &&\n                  !context.directiveTransforms[p.name],\n              ))))\n      ) {\n        return\n      }\n\n      // テキストノードを createTextVNode(text) 呼び出しに変換\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {\n          const callArgs: any[] = []\n          // createTextVNode はデフォルトで単一スペースなので，\n          // 単一スペースの場合は引数を省略できる\n          if (child.type !== NodeTypes.TEXT || child.content !== ' ') {\n            callArgs.push(child)\n          }\n          // 動的テキストにフラグを付けてブロック内でパッチされるようにする\n          if (!context.ssr && !isStaticNode(child)) {\n            callArgs.push(PatchFlags.TEXT)\n          }\n          children[i] = {\n            type: NodeTypes.TEXT_CALL,\n            content: child,\n            loc: child.loc,\n            codegenNode: createCallExpression(\n              context.helper(CREATE_TEXT),\n              callArgs,\n            ),\n          }\n        }\n      }\n    }\n  }\n}\n\nfunction isStaticNode(node: any): boolean {\n  if (node.type === NodeTypes.TEXT) {\n    return true\n  }\n  if (node.type === NodeTypes.INTERPOLATION) {\n    return node.content.isStatic\n  }\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    return node.children.every((child: any) => {\n      if (typeof child === 'string') return true\n      return isStaticNode(child)\n    })\n  }\n  return false\n}\n```\n\n`packages/compiler-core/src/utils.ts` に `isText` ヘルパーを追加します．\n\n```ts\nexport function isText(\n  node: TemplateChildNode,\n): node is TextNode | InterpolationNode {\n  return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION\n}\n```\n\n`packages/compiler-core/src/ast.ts` に `TEXT_CALL` ノードタイプと `createCallExpression` を追加します．\n\n```ts\nexport const enum NodeTypes {\n  // ... 既存のタイプ ...\n  TEXT_CALL, // [!code ++]\n}\n\nexport interface TextCallNode extends Node {\n  type: NodeTypes.TEXT_CALL\n  content: TextNode | InterpolationNode | CompoundExpressionNode\n  codegenNode: CallExpression\n}\n\nexport function createCallExpression(\n  callee: string,\n  args: CallExpression['arguments'] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  }\n}\n```\n\n`packages/compiler-core/src/runtimeHelpers.ts` に `CREATE_TEXT` を追加します．\n\n```ts\nexport const CREATE_TEXT = Symbol('createTextVNode')\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ... 既存のヘルパー ...\n  [CREATE_TEXT]: 'createTextVNode',\n}\n```\n\n### トランスフォーマーの登録\n\n`packages/compiler-core/src/compile.ts` でトランスフォーマーを登録します．\n\n```ts\nimport { transformText } from './transforms/transformText'\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformElement,\n      transformSlotOutlet,\n      transformText, // [!code ++]\n    ],\n    {\n      on: transformOn,\n      bind: transformBind,\n      if: transformIf,\n      for: transformFor,\n      model: transformModel,\n    },\n  ]\n}\n```\n\n### コード生成の更新\n\n`packages/compiler-core/src/codegen.ts` に `TEXT_CALL` ノードの処理を追加します．\n\n```ts\nfunction genNode(node: any, context: CodegenContext) {\n  switch (node.type) {\n    // ... 既存のケース ...\n    case NodeTypes.TEXT_CALL: // [!code ++]\n      genNode(node.codegenNode, context) // [!code ++]\n      break // [!code ++]\n  }\n}\n```\n\n### ランタイムの更新\n\n`packages/runtime-core/src/vnode.ts` に `createTextVNode` を追加します．\n\n```ts\nexport function createTextVNode(text: string = ' ', flag: number = 0): VNode {\n  return createVNode(Text, null, text, flag)\n}\n```\n\nこれを `packages/runtime-core/src/index.ts` からエクスポートします．\n\n```ts\nexport { createTextVNode } from './vnode'\n```\n\n## 動作確認\n\n以下のようなテンプレートで動作を確認してみましょう．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const name = ref('World')\n    return { name }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello {{ name }}!</p>\n  </div>\n</template>\n```\n\nコンパイル結果を確認すると，以下のようになるはずです:\n- 不要なホワイトスペース（改行・インデント）が削除されている\n- `Hello ` と `{{ name }}` と `!` が結合されている\n\nこれでコンパイラの品質が向上しました！\n\nここまでのソースコード:\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/100_chore_compiler)\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/110-parser-optimization.md",
    "content": "# パーサーの最適化\n\n::: info この章について\nこの章では，Vue 3.4 で導入された新しいパーサーアーキテクチャについて解説します．\\\nhtmlparser2 をベースにした state-machine tokenizer により，パース速度が 2 倍に向上しました．\n:::\n\n## 背景\n\nVue 3.4 では，テンプレートコンパイラの内部実装が大幅にリファクタリングされました．これまでの chibivue で実装してきたパーサーは，Vue 3.3 以前のアーキテクチャに基づいています．\n\n### 従来のパーサー（Vue 3.3 以前）\n\n従来の Vue のパーサーは**再帰下降パーサー（recursive descent parser）**でした:\n\n```ts\n// 従来の実装イメージ\nfunction parseChildren(context: ParserContext): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined\n\n    if (startsWith(s, '{{')) {\n      node = parseInterpolation(context)\n    } else if (s[0] === '<') {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context)\n      }\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\nこの方式の問題点:\n- 多くの**正規表現**を使用\n- **先読み（look-ahead）検索**が頻繁に発生\n- テンプレート文字列を何度も走査\n\n### 新しいパーサー（Vue 3.4）\n\nVue 3.4 では，[htmlparser2](https://github.com/fb55/htmlparser2) の tokenizer をベースにした**ステートマシン tokenizer**が導入されました:\n\n```ts\n// 新しい実装イメージ\nconst enum State {\n  Text,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  BeforeAttrName,\n  InAttrName,\n  // ...\n}\n\nclass Tokenizer {\n  private state = State.Text\n  private index = 0\n\n  parse(input: string) {\n    for (let i = 0; i < input.length; i++) {\n      this.index = i\n      this.consume(input.charCodeAt(i))\n    }\n  }\n\n  private consume(char: number) {\n    switch (this.state) {\n      case State.Text:\n        this.handleText(char)\n        break\n      case State.BeforeTagName:\n        this.handleBeforeTagName(char)\n        break\n      // ...\n    }\n  }\n}\n```\n\nこの方式の利点:\n- テンプレート文字列を**一度だけ走査**\n- 正規表現を使用しない（または最小限）\n- **文字単位**で処理するため効率的\n- **状態遷移**が明確で保守性が高い\n\n<KawaikoNote variant=\"surprise\" title=\"2 倍速！\">\n\nこの state-machine tokenizer により，パース速度が**一貫して 2 倍**に向上しました！\\\n正規表現や先読み検索を避け，一文字ずつ順番に処理するだけで大幅な高速化が実現できるのは驚きですね．\n\n</KawaikoNote>\n\n## State Machine Tokenizer\n\nステートマシン tokenizer は，現在の状態（state）に基づいて次の文字をどう処理するかを決定します．\n\n### 状態の定義\n\n```ts\nconst enum State {\n  // テキスト\n  Text = 1,\n\n  // 補間（Mustache）\n  InterpolationOpen,     // {{ を検出中\n  Interpolation,         // {{ 内のコンテンツ\n  InterpolationClose,    // }} を検出中\n\n  // タグ\n  BeforeTagName,         // < の後\n  InTagName,             // タグ名の中\n  InSelfClosingTag,      // /> を検出中\n\n  // 属性\n  BeforeAttrName,        // 属性名の前\n  InAttrName,            // 属性名の中\n  AfterAttrName,         // 属性名の後（= の前）\n  BeforeAttrValue,       // 属性値の前\n  InAttrValueDq,         // ダブルクォート内の属性値\n  InAttrValueSq,         // シングルクォート内の属性値\n  InAttrValueNq,         // クォートなしの属性値\n\n  // ディレクティブ\n  InDirName,             // ディレクティブ名（v-xxx）\n  InDirArg,              // ディレクティブ引数（:xxx）\n  InDirDynamicArg,       // 動的引数（[xxx]）\n  InDirModifier,         // 修飾子（.xxx）\n}\n```\n\n### 状態遷移の例\n\n```\n<div v-if=\"show\">Hello {{ name }}</div>\n```\n\nこの例での状態遷移:\n\n```\n< → BeforeTagName\nd → InTagName\ni → InTagName\nv → InTagName\n(space) → BeforeAttrName\nv → InAttrName (or InDirName)\n- → InDirName\ni → InDirName\nf → InDirName\n= → BeforeAttrValue\n\" → InAttrValueDq\ns → InAttrValueDq\nh → InAttrValueDq\no → InAttrValueDq\nw → InAttrValueDq\n\" → BeforeAttrName\n> → Text\nH → Text\n...\n{ → InterpolationOpen\n{ → Interpolation\n(space) → Interpolation\nn → Interpolation\na → Interpolation\nm → Interpolation\ne → Interpolation\n(space) → Interpolation\n} → InterpolationClose\n} → Text\n...\n```\n\n## Visitor パターン\n\n新しいパーサーでは，**Visitor パターン**を使用して tokenizer と AST 構築を分離しています．\n\n### Callbacks Interface\n\n```ts\ninterface Callbacks {\n  onText(start: number, end: number): void\n  onInterpolation(start: number, end: number): void\n  onOpenTag(tag: string, start: number): void\n  onCloseTag(tag: string, start: number, end: number): void\n  onSelfClosingTag(tag: string, start: number, end: number): void\n  onAttr(name: string, value: string | undefined, start: number, end: number): void\n  onDirective(\n    name: string,\n    arg: string | undefined,\n    modifiers: string[],\n    value: string | undefined,\n    start: number,\n    end: number\n  ): void\n  onComment(start: number, end: number): void\n}\n```\n\n### Tokenizer と Parser の分離\n\n```ts\nclass Tokenizer {\n  private cbs: Callbacks\n\n  constructor(callbacks: Callbacks) {\n    this.cbs = callbacks\n  }\n\n  // tokenizer がイベントを発行\n  private emitOpenTag(tag: string, start: number) {\n    this.cbs.onOpenTag(tag, start)\n  }\n\n  private emitText(start: number, end: number) {\n    this.cbs.onText(start, end)\n  }\n}\n\n// Parser は Callbacks を実装して AST を構築\nclass Parser implements Callbacks {\n  private stack: ElementNode[] = []\n  private root: RootNode\n\n  onOpenTag(tag: string, start: number) {\n    const element: ElementNode = {\n      type: NodeTypes.ELEMENT,\n      tag,\n      children: [],\n      // ...\n    }\n    this.stack.push(element)\n  }\n\n  onCloseTag(tag: string, start: number, end: number) {\n    const element = this.stack.pop()!\n    const parent = this.stack[this.stack.length - 1]\n    if (parent) {\n      parent.children.push(element)\n    } else {\n      this.root.children.push(element)\n    }\n  }\n\n  onText(start: number, end: number) {\n    const parent = this.stack[this.stack.length - 1]\n    const text: TextNode = {\n      type: NodeTypes.TEXT,\n      content: this.source.slice(start, end),\n      // ...\n    }\n    parent.children.push(text)\n  }\n}\n```\n\n### メリット\n\n1. **関心の分離**: Tokenizer は文字の解析のみ，Parser は AST 構築のみに集中\n2. **テスタビリティ**: 各コンポーネントを独立してテスト可能\n3. **再利用性**: Tokenizer を他の目的（シンタックスハイライト，Lint など）に再利用可能\n4. **パフォーマンス**: 不要な中間データ構造を生成しない\n\n<KawaikoNote variant=\"question\" title=\"Visitor パターンって？\">\n\nVisitor パターンは「データ構造とその処理を分離する」設計パターンです．\\\nTokenizer は「テンプレートを読んでイベントを発行するだけ」，Parser は「イベントを受け取って AST を作るだけ」というシンプルな責務分担になっています．\\\nこれにより，コードが理解しやすく，テストもしやすくなります！\n\n</KawaikoNote>\n\n## パフォーマンス比較\n\nVue 3.4 のブログ記事によると:\n\n| テンプレートサイズ | 改善率 |\n|-----------------|-------|\n| 小規模 | 約 2x |\n| 中規模 | 約 2x |\n| 大規模 | 約 2x |\n\n一貫して 2 倍の高速化が実現されています．\n\nこの改善はエコシステム全体に波及します:\n- **Volar**: IDE の補完・型チェック\n- **vue-tsc**: 型チェック\n- **ビルドツール**: Vite, Webpack など\n- **コミュニティプラグイン**: ESLint, Prettier など\n\n## chibivue での実装\n\n::: warning\n現在の chibivue は従来の再帰下降パーサーを使用しています．\\\nVue 3.4 スタイルの tokenizer への移行は，今後の課題として検討されています．\n:::\n\n基本的な実装のアウトラインは以下の通りです:\n\n<KawaikoNote variant=\"base\" title=\"興味があれば挑戦！\">\n\nこの章で紹介した state-machine tokenizer は，chibivue ではまだ実装していませんが，興味があれば自分で実装してみてください！\\\nVue 3.4 のソースコードや htmlparser2 を参考にすると，理解が深まります．\\\nパーサーの最適化は，フレームワーク開発において非常に重要なスキルです．\n\n</KawaikoNote>\n\n```ts\n// packages/compiler-core/tokenizer.ts\nconst enum State {\n  Text = 1,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  // ...\n}\n\nconst enum CharCodes {\n  Lt = 0x3c,      // <\n  Gt = 0x3e,      // >\n  Slash = 0x2f,   // /\n  Eq = 0x3d,      // =\n  OpenBrace = 0x7b,  // {\n  CloseBrace = 0x7d, // }\n  // ...\n}\n\nexport class Tokenizer {\n  private state = State.Text\n  private buffer = ''\n  private sectionStart = 0\n  private index = 0\n\n  constructor(private cbs: Callbacks) {}\n\n  parse(input: string) {\n    this.buffer = input\n    while (this.index < input.length) {\n      const c = input.charCodeAt(this.index)\n      switch (this.state) {\n        case State.Text:\n          this.stateText(c)\n          break\n        case State.InterpolationOpen:\n          this.stateInterpolationOpen(c)\n          break\n        // ...\n      }\n      this.index++\n    }\n    this.finish()\n  }\n\n  private stateText(c: number) {\n    if (c === CharCodes.Lt) {\n      if (this.index > this.sectionStart) {\n        this.cbs.onText(this.sectionStart, this.index)\n      }\n      this.state = State.BeforeTagName\n      this.sectionStart = this.index\n    } else if (c === CharCodes.OpenBrace) {\n      this.state = State.InterpolationOpen\n    }\n  }\n\n  private stateInterpolationOpen(c: number) {\n    if (c === CharCodes.OpenBrace) {\n      if (this.index > this.sectionStart + 1) {\n        this.cbs.onText(this.sectionStart, this.index - 1)\n      }\n      this.state = State.Interpolation\n      this.sectionStart = this.index + 1\n    } else {\n      this.state = State.Text\n    }\n  }\n\n  // ...\n}\n```\n\n## まとめ\n\n- Vue 3.4 で htmlparser2 ベースの state-machine tokenizer が導入された\n- テンプレート文字列を一度だけ走査することでパース速度が 2 倍に向上\n- Visitor パターンにより tokenizer と AST 構築が分離され，保守性が向上\n- この最適化はエコシステム全体（Volar, vue-tsc など）に恩恵をもたらす\n\n## 参考リンク\n\n- [Announcing Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) - Vue 公式ブログ\n- [htmlparser2](https://github.com/fb55/htmlparser2) - Tokenizer のベースとなったライブラリ\n- [Vue 3.4 Parser Refactor](https://github.com/vuejs/core/pull/9674) - GitHub PR\n"
  },
  {
    "path": "book/online-book/src/ja/50-basic-template-compiler/500-custom-directive.md",
    "content": "# カスタムディレクティブ\n\n::: info この章について\nこの章では，Vue のカスタムディレクティブ機能を実装します．\\\n`v-focus` のような独自のディレクティブを定義し，要素に対して直接操作を行う方法を学びます．\n:::\n\n## カスタムディレクティブとは\n\nVue のカスタムディレクティブは，DOM 要素に対して低レベルの操作を行うための機能です．コンポーネントの抽象化では対応しきれないような，DOM の直接操作が必要な場面で使用されます．\n\n典型的な使用例：\n\n- 要素への自動フォーカス（`v-focus`）\n- クリック外検出（`v-click-outside`）\n- 要素の遅延読み込み（`v-lazy`）\n- ツールチップの表示（`v-tooltip`）\n\n```vue\n<script setup>\n// カスタムディレクティブの定義\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n</script>\n\n<template>\n  <input v-focus />\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"正直あまり使わない\">\n\nカスタムディレクティブは「DOM を直接触りたいとき」に使いますが，正直あまり使われていません．\\\nVapor Mode での実装変更や静的解析との相性の悪さもあり，**使わなくていいなら使わなくていい** という機能です．\\\nコンポーネントで対応できることは基本的にコンポーネントで行いましょう！\n\n</KawaikoNote>\n\n## ディレクティブのライフサイクル\n\nディレクティブにはコンポーネントと同様にライフサイクルフックがあります：\n\n```ts\nconst myDirective = {\n  // 要素の属性や イベントリスナーが適用される前\n  created(el, binding, vnode, prevVnode) {},\n\n  // 要素が DOM に挿入される直前\n  beforeMount(el, binding, vnode, prevVnode) {},\n\n  // 要素が DOM に挿入された後\n  mounted(el, binding, vnode, prevVnode) {},\n\n  // 親コンポーネントが更新される前\n  beforeUpdate(el, binding, vnode, prevVnode) {},\n\n  // 親コンポーネントと子の更新後\n  updated(el, binding, vnode, prevVnode) {},\n\n  // 親コンポーネントがアンマウントされる前\n  beforeUnmount(el, binding, vnode, prevVnode) {},\n\n  // 親コンポーネントがアンマウントされた後\n  unmounted(el, binding, vnode, prevVnode) {},\n}\n```\n\n各フックには以下の引数が渡されます：\n\n- `el`: ディレクティブがバインドされた要素\n- `binding`: ディレクティブに渡された情報（値，引数など）\n- `vnode`: el に対応する VNode\n- `prevVnode`: 更新前の VNode（beforeUpdate, updated のみ）\n\n## 実装の概要\n\nカスタムディレクティブの実装は 3 つの部分から構成されています：\n\n1. **ランタイム側**: ディレクティブの型定義と `withDirectives` ヘルパー\n2. **レンダラー側**: 各ライフサイクルでのフック呼び出し\n3. **コンパイラ側**: テンプレートから `withDirectives` を生成\n\n## ランタイムの実装\n\n### ディレクティブの型定義\n\nまず，ディレクティブの型を定義します：\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentPublicInstance | null\n  value: V\n  oldValue: V | null\n  arg?: string\n  dir: ObjectDirective<any>\n}\n\nexport type DirectiveHook<T = any> = (\n  el: T,\n  binding: DirectiveBinding,\n  vnode: VNode,\n  prevVNode: VNode | null\n) => void\n\nexport interface ObjectDirective<T = any> {\n  created?: DirectiveHook<T>\n  beforeMount?: DirectiveHook<T>\n  mounted?: DirectiveHook<T>\n  beforeUpdate?: DirectiveHook<T>\n  updated?: DirectiveHook<T>\n  beforeUnmount?: DirectiveHook<T>\n  unmounted?: DirectiveHook<T>\n}\n```\n\n### withDirectives ヘルパー\n\nコンパイラは，ディレクティブ付きの要素を `withDirectives` でラップしたコードを生成します：\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport type DirectiveArguments = Array<\n  | [ObjectDirective | undefined]\n  | [ObjectDirective | undefined, any]\n  | [ObjectDirective | undefined, any, string]\n>\n\nexport function withDirectives<T extends VNode>(\n  vnode: T,\n  directives: DirectiveArguments\n): T {\n  const internalInstance = currentRenderingInstance\n  if (internalInstance === null) return vnode\n\n  const instance = internalInstance.proxy\n\n  const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])\n  for (let i = 0; i < directives.length; i++) {\n    let [dir, value, arg] = directives[i]\n    if (dir) {\n      // 関数形式のディレクティブをオブジェクト形式に変換\n      if (isFunction(dir)) {\n        dir = {\n          mounted: dir,\n          updated: dir,\n        } as ObjectDirective\n      }\n      bindings.push({\n        dir,\n        instance,\n        value,\n        oldValue: void 0,\n        arg,\n      })\n    }\n  }\n  return vnode\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"シンプル！\">\n\n`withDirectives` は VNode に `dirs` プロパティを追加するだけです．\\\n実際のフック呼び出しはレンダラーが行うので，ここでは情報を VNode に付与するだけの単純な実装です！\n\n</KawaikoNote>\n\n### ディレクティブフックの呼び出し\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport function invokeDirectiveHook(\n  vnode: VNode,\n  prevVNode: VNode | null,\n  name: keyof ObjectDirective\n): void {\n  const bindings = vnode.dirs!\n  const oldBindings = prevVNode && prevVNode.dirs!\n\n  for (let i = 0; i < bindings.length; i++) {\n    const binding = bindings[i]\n    // 更新時は前の値を設定\n    if (oldBindings) {\n      binding.oldValue = oldBindings[i].value\n    }\n\n    const hook = binding.dir[name] as DirectiveHook | undefined\n    if (hook) {\n      hook(vnode.el, binding, vnode, prevVNode)\n    }\n  }\n}\n```\n\n## レンダラーの実装\n\nレンダラーでは，要素のマウントと更新の各タイミングで `invokeDirectiveHook` を呼び出します：\n\n```ts\n// packages/runtime-core/src/renderer.ts\n\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const { type, props, children, dirs } = vnode\n\n  const el = (vnode.el = hostCreateElement(type as string))\n\n  // 子要素のマウント\n  if (typeof children === 'string') {\n    hostSetElementText(el, children)\n  } else if (isArray(children)) {\n    mountChildren(children as VNodeArrayChildren, el, null, parentComponent)\n  }\n\n  // ディレクティブ: created フック\n  dirs && invokeDirectiveHook(vnode, null, 'created')\n\n  // props の設定\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, null, props[key])\n    }\n  }\n\n  // ディレクティブ: beforeMount フック\n  dirs && invokeDirectiveHook(vnode, null, 'beforeMount')\n\n  // DOM への挿入\n  hostInsert(el, container, anchor!)\n\n  // ディレクティブ: mounted フック\n  dirs && invokeDirectiveHook(vnode, null, 'mounted')\n}\n\nconst patchElement = (\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const el = (n2.el = n1.el!)\n  const { dirs } = n2\n  const oldProps = n1.props ?? {}\n  const newProps = n2.props ?? {}\n\n  // ディレクティブ: beforeUpdate フック\n  dirs && invokeDirectiveHook(n2, n1, 'beforeUpdate')\n\n  // 子要素と props の更新\n  patchChildren(n1, n2, el, null, parentComponent)\n  patchProps(el, oldProps, newProps)\n\n  // ディレクティブ: updated フック\n  dirs && invokeDirectiveHook(n2, n1, 'updated')\n}\n```\n\n## VNode への dirs プロパティ追加\n\nVNode の型定義に `dirs` を追加します：\n\n```ts\n// packages/runtime-core/src/vnode.ts\n\nexport interface VNode<ExtraProps = { [key: string]: any }> {\n  type: VNodeTypes\n  props: (VNodeProps & ExtraProps) | null\n  children: VNodeNormalizedChildren\n  el: RendererNode | null\n  key: string | number | symbol | null\n  ref: Ref | null\n  shapeFlag: number\n  dirs?: DirectiveBinding[] | null  // 追加\n}\n```\n\n## コンパイラの実装\n\n### WITH_DIRECTIVES ヘルパーの登録\n\n```ts\n// packages/compiler-core/src/runtimeHelpers.ts\n\nexport const WITH_DIRECTIVES: unique symbol = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_DIRECTIVES]: 'withDirectives',\n}\n```\n\n### コード生成\n\nVNode にディレクティブがある場合，`withDirectives` でラップします：\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper } = context\n  const { tag, props, children, directives } = node\n\n  // ディレクティブがある場合は withDirectives でラップ\n  if (directives) {\n    push(helper(WITH_DIRECTIVES) + `(`)\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`, node)\n  genNodeList(genNullableArgs([tag, props, children]), context)\n  push(`)`)\n\n  if (directives) {\n    push(`, `)\n    genNode(directives, context)\n    push(`)`)\n  }\n}\n```\n\n生成されるコードの例：\n\n```ts\n// テンプレート: <input v-focus />\n\n// 生成されるコード\nwithDirectives(\n  createElementVNode('input'),\n  [[vFocus]]\n)\n\n// テンプレート: <div v-my-directive:arg.modifier=\"value\" />\n\n// 生成されるコード\nwithDirectives(\n  createElementVNode('div'),\n  [[vMyDirective, value, 'arg', { modifier: true }]]\n)\n```\n\n## 動作確認\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\n\n// v-focus ディレクティブ\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n\n// v-color ディレクティブ\nconst vColor = {\n  mounted(el, binding) {\n    el.style.color = binding.value\n  },\n  updated(el, binding) {\n    el.style.color = binding.value\n  }\n}\n\nconst color = ref('red')\n</script>\n\n<template>\n  <input v-focus placeholder=\"自動フォーカス\" />\n\n  <p v-color=\"color\">この文字は {{ color }} 色です</p>\n\n  <button @click=\"color = 'blue'\">青にする</button>\n  <button @click=\"color = 'green'\">緑にする</button>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"実装完了！\">\n\nカスタムディレクティブの実装が完了しました！\\\nランタイム，レンダラー，コンパイラの 3 つが連携して動作することで，`v-focus` のような独自ディレクティブが使えるようになりました．\\\nv-model も内部的にはディレクティブとして実装されていますので，ぜひ確認してみてください！\n\n</KawaikoNote>\n\n## まとめ\n\n- カスタムディレクティブは DOM を直接操作する低レベル API\n- `withDirectives` で VNode にディレクティブ情報を付与\n- レンダラーが各ライフサイクルでフックを呼び出し\n- コンパイラはテンプレートから `withDirectives` を生成\n\n## 参考リンク\n\n- [Vue.js - カスタムディレクティブ](https://vuejs.org/guide/reusability/custom-directives.html) - Vue 公式ドキュメント\n"
  },
  {
    "path": "book/online-book/src/ja/60-basic-sfc-compiler/010-script-setup.md",
    "content": "# script setup に対応する\n\n::: info この章について\nこの章では，Vue 3 で導入された `<script setup>` 構文の実装方法を学びます．\\\nより簡潔にコンポーネントを記述できる script setup の仕組みを理解しましょう．\n:::\n\n## script setup とは\n\n`<script setup>` は，Vue 3.2 で導入されたコンパイル時のシンタックスシュガーです．従来の Options API や Composition API に比べて，より簡潔にコンポーネントを記述できます．\n\n```vue\n<!-- 従来の書き方 -->\n<script>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: { MyComponent },\n  setup() {\n    const count = ref(0)\n    const increment = () => count.value++\n    return { count, increment }\n  }\n}\n</script>\n\n<!-- script setup の書き方 -->\n<script setup>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n```\n\n<KawaikoNote variant=\"surprise\" title=\"こんなに短く！\">\n\nscript setup を使うと，`export default` や `return` が不要になり，インポートしたコンポーネントも自動的に登録されます．\\\nコードがとてもスッキリしますね！\n\n</KawaikoNote>\n\n## 実装の概要\n\nscript setup のコンパイルは以下のステップで行われます：\n\n1. **インポートの解析とホイスト**: import 文を抽出してファイルの先頭に移動\n2. **バインディングの解析**: 変数宣言や関数定義を追跡\n3. **マクロの処理**: defineProps, defineEmits などの処理（次章以降）\n4. **コード変換**: setup 関数への変換と return 文の生成\n\n## compileScript 関数\n\n`compileScript` 関数は，SFC のスクリプト部分をコンパイルする中心的な関数です．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nexport function compileScript(\n  sfc: SFCDescriptor,\n  options: SFCScriptCompileOptions,\n): SFCScriptBlock {\n  let { script, scriptSetup, source } = sfc\n\n  // Babel でパース\n  const scriptAst = _parse(script?.content ?? \"\", { sourceType: \"module\" }).program\n  const scriptSetupAst = _parse(scriptSetup?.content ?? \"\", { sourceType: \"module\" }).program\n\n  // script setup がない場合は従来の処理\n  if (!scriptSetup) {\n    if (!script) {\n      throw new Error(`SFC contains no <script> tags.`)\n    }\n    return { ...script, bindings: analyzeScriptBindings(scriptAst.body) }\n  }\n\n  // メタデータの初期化\n  const bindingMetadata: BindingMetadata = {}\n  const userImports: Record<string, ImportBinding> = Object.create(null)\n  const setupBindings: Record<string, BindingTypes> = Object.create(null)\n\n  const s = new MagicString(source)\n  // ... 変換処理\n}\n```\n\n## インポートのホイスト\n\nscript setup 内のインポート文は，生成されるコードの先頭に移動（ホイスト）する必要があります．\n\n```ts\n// 1.2 walk import declarations of <script setup>\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ImportDeclaration\") {\n    // インポートをファイル先頭に移動\n    hoistNode(node)\n\n    // 重複インポートの除去\n    for (let i = 0; i < node.specifiers.length; i++) {\n      const specifier = node.specifiers[i]\n      const local = specifier.local.name\n      const imported = getImportedName(specifier)\n      const source = node.source.value\n\n      const existing = userImports[local]\n      if (existing) {\n        if (existing.source === source && existing.imported === imported) {\n          removeSpecifier(i)\n        }\n      } else {\n        registerUserImport(source, local, imported, true)\n      }\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"question\" title=\"なぜホイストが必要？\">\n\n生成されるコードでは，インポート文は `setup()` 関数の外に配置される必要があります．\\\n`<script setup>` 内に書かれたインポートを正しい位置に移動するのがホイストです．\n\nちなみに `<script setup>` 内での `export` はエラーになります．\\\nただし `export type` は型情報のみなので OK です！\n\n</KawaikoNote>\n\n## バインディングの解析\n\nテンプレートから参照される変数を正しく解決するため，スクリプト内のバインディングを解析します．\n\n```ts\nfunction walkDeclaration(\n  node: Declaration,\n  bindings: Record<string, BindingTypes>,\n  userImportAliases: Record<string, string> = {},\n) {\n  if (node.type === \"VariableDeclaration\") {\n    const isConst = node.kind === \"const\"\n\n    for (const { id, init } of node.declarations) {\n      if (id.type === \"Identifier\") {\n        let bindingType\n        if (isConst && isStaticNode(init!)) {\n          bindingType = BindingTypes.LITERAL_CONST\n        } else if (isCallOf(init, userImportAliases[\"reactive\"])) {\n          bindingType = BindingTypes.SETUP_REACTIVE_CONST\n        } else if (isCallOf(init, userImportAliases[\"ref\"])) {\n          bindingType = BindingTypes.SETUP_REF\n        } else if (isConst) {\n          bindingType = BindingTypes.SETUP_MAYBE_REF\n        } else {\n          bindingType = BindingTypes.SETUP_LET\n        }\n        registerBinding(bindings, id, bindingType)\n      }\n    }\n  } else if (node.type === \"FunctionDeclaration\") {\n    bindings[node.id!.name] = BindingTypes.SETUP_CONST\n  }\n}\n```\n\nバインディングタイプによって，テンプレート内での参照方法が変わります：\n\n| タイプ | 説明 | テンプレートでの参照 |\n|--------|------|---------------------|\n| `SETUP_REF` | ref() で作成 | `.value` を自動追加 |\n| `SETUP_REACTIVE_CONST` | reactive() で作成 | そのまま参照 |\n| `SETUP_CONST` | 定数 | そのまま参照 |\n| `SETUP_LET` | let/var 変数 | そのまま参照 |\n\n## インラインテンプレート\n\nscript setup を使う場合，テンプレートは setup 関数内にインライン化できます．\n\n```ts\n// 10. generate return statement\nlet returned\nif (options.inlineTemplate) {\n  if (sfc.template) {\n    const { code, preamble } = compileTemplate({\n      source: sfc.template.content.trim(),\n      compilerOptions: { inline: true, bindingMetadata },\n    })\n\n    if (preamble) {\n      s.prepend(preamble)\n    }\n    returned = code\n  } else {\n    returned = `() => {}`\n  }\n}\ns.appendRight(endOffset, `\\nreturn ${returned}\\n`)\n```\n\n生成されるコードの例：\n\n```ts\n// 入力\n// <script setup>\n// import { ref } from 'chibivue'\n// const count = ref(0)\n// </script>\n// <template>\n//   <p>{{ count }}</p>\n// </template>\n\n// 出力\nimport { ref } from 'chibivue'\n\nexport default {\n  setup(__props) {\n    const count = ref(0)\n\n    return (_ctx) => {\n      return h('p', count.value)\n    }\n  }\n}\n```\n\n## Vite プラグインとの連携\n\nVite プラグインでは，script setup の検出とコンパイルを行います．\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/script.ts\n\nexport function resolveScript(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n): SFCScriptBlock | null {\n  if (!descriptor.script && !descriptor.scriptSetup) return null\n\n  return options.compiler.compileScript(descriptor, {\n    inlineTemplate: isUseInlineTemplate(descriptor),\n  })\n}\n\nexport function isUseInlineTemplate(descriptor: SFCDescriptor): boolean {\n  return !!descriptor.scriptSetup\n}\n```\n\n## 動作確認\n\n```vue\n<script setup>\nimport { ref, computed } from 'chibivue'\n\nconst count = ref(0)\nconst double = computed(() => count.value * 2)\n\nconst increment = () => {\n  count.value++\n}\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ count }}</p>\n    <p>Double: {{ double }}</p>\n    <button @click=\"increment\">+1</button>\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"実装完了！\">\n\nscript setup の基本実装が完了しました！\\\n従来の書き方に比べてずっと簡潔にコンポーネントを記述できるようになりました．\\\n次の章では，`defineProps` と `defineEmits` マクロの実装を学びます．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/010_script_setup)\n\n## まとめ\n\n- `<script setup>` は Composition API をより簡潔に書けるシンタックスシュガー\n- `compileScript` が中心的な変換処理を担当\n- インポートのホイストとバインディング解析が重要なステップ\n- テンプレートは setup 関数内にインライン化される\n\n## 参考リンク\n\n- [Vue.js - script setup](https://vuejs.org/api/sfc-script-setup.html) - Vue 公式ドキュメント\n- [RFC: script setup](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/ja/60-basic-sfc-compiler/020-define-props.md",
    "content": "# defineProps に対応する\n\n::: info この章について\nこの章では，`<script setup>` で使用する `defineProps` マクロの実装方法を学びます．\\\nコンパイラマクロの仕組みと，props の宣言がどのように処理されるかを理解しましょう．\n:::\n\n## defineProps とは\n\n`defineProps` は `<script setup>` 内でコンポーネントの props を宣言するためのコンパイラマクロです．\n\n```vue\n<script setup>\n// ランタイム宣言\nconst props = defineProps({\n  title: String,\n  count: {\n    type: Number,\n    default: 0\n  }\n})\n\nconsole.log(props.title)\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"コンパイラマクロって？\">\n\n`defineProps` は通常の関数ではありません．**コンパイラマクロ**です．\\\nコンパイル時に特別な処理が行われ，実行時には消去されます．\\\nそのため，import なしで使えるんです！\n\n</KawaikoNote>\n\n## 実装の概要\n\ndefineProps の処理は以下のステップで行われます：\n\n1. **マクロ呼び出しの検出**: AST から `defineProps()` の呼び出しを見つける\n2. **引数の抽出**: props の定義オブジェクトを取得\n3. **コードの削除**: 元の `defineProps()` 呼び出しを削除\n4. **オプションへの追加**: `props` オプションとして出力に追加\n5. **バインディングの登録**: props を `PROPS` タイプとして登録\n\n## processDefineProps 関数\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_PROPS = \"defineProps\"\n\nlet propsRuntimeDecl: Node | undefined\nlet propsIdentifier: string | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  // 引数（props定義オブジェクト）を保存\n  propsRuntimeDecl = node.arguments[0]\n\n  // 変数に代入されている場合は識別子を保存\n  // const props = defineProps(...) の \"props\" 部分\n  if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST の走査\n\n`<script setup>` の本文を走査して `defineProps` を検出します．\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  // 式文の場合（defineProps() 単体で呼び出された場合）\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr)) {\n      // マクロ呼び出しを削除\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  // 変数宣言の場合（const props = defineProps(...)）\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        if (processDefineProps(init, declId)) {\n          // 宣言を削除\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## props バインディングの登録\n\nprops として宣言された変数は，テンプレートから参照できるようにバインディングメタデータに登録します．\n\n```ts\n// 7. analyze binding metadata\nif (propsRuntimeDecl) {\n  for (const key of getObjectExpressionKeys(propsRuntimeDecl as ObjectExpression)) {\n    bindingMetadata[key] = BindingTypes.PROPS\n  }\n}\n```\n\n`BindingTypes.PROPS` として登録することで，テンプレートコンパイラは props へのアクセスを正しく処理できます．\n\n## props 識別子の処理\n\n`const props = defineProps(...)` のように変数に代入された場合，その変数で props にアクセスできるようにします．\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\nif (propsIdentifier) {\n  // const props = __props; を追加\n  s.prependLeft(startOffset, `\\nconst ${propsIdentifier} = __props;\\n`)\n}\n```\n\n## オプションへの追加\n\n最終的に，props 定義はコンポーネントオプションとして出力されます．\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  let declCode = scriptSetup.content\n    .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)\n    .trim()\n  runtimeOptions += `\\n  props: ${declCode},`\n}\n\ns.prependLeft(\n  startOffset,\n  `\\nexport default {\\n${runtimeOptions}\\nsetup(${args}) {\\n`\n)\n```\n\n## 変換結果の例\n\n```vue\n<!-- 入力 -->\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n\n<template>\n  <h1>{{ title }}</h1>\n</template>\n```\n\n```ts\n// 出力\nexport default {\n  props: {\n    title: String,\n    count: Number\n  },\n  setup(__props) {\n    const props = __props;\n\n    return (_ctx) => {\n      return h('h1', _ctx.title)\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"シンプル！\">\n\n`defineProps` は複雑そうに見えますが，やっていることはシンプル：\n1. 引数を `props` オプションに移動\n2. `defineProps()` 呼び出しを削除\n3. 変数があれば `__props` への参照に置き換え\n\n</KawaikoNote>\n\n## 動作確認\n\n```vue\n<script setup>\nimport { computed } from 'chibivue'\n\nconst props = defineProps({\n  firstName: String,\n  lastName: String\n})\n\nconst fullName = computed(() => `${props.firstName} ${props.lastName}`)\n</script>\n\n<template>\n  <div>\n    <p>First: {{ firstName }}</p>\n    <p>Last: {{ lastName }}</p>\n    <p>Full: {{ fullName }}</p>\n  </div>\n</template>\n```\n\n親コンポーネント：\n\n```vue\n<script setup>\nimport ChildComponent from './ChildComponent.vue'\n</script>\n\n<template>\n  <ChildComponent firstName=\"John\" lastName=\"Doe\" />\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"実装完了！\">\n\ndefineProps の実装が完了しました！\\\nコンパイラマクロの基本的な仕組みを理解できましたね．\\\n次の章では `defineEmits` マクロの実装を学びます．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/020_define_props)\n\n## まとめ\n\n- `defineProps` はコンパイラマクロで，コンパイル時に処理される\n- AST を走査して `defineProps()` 呼び出しを検出\n- 引数は `props` オプションに変換され，呼び出し自体は削除\n- props は `BindingTypes.PROPS` として登録され，テンプレートから参照可能\n\n## 参考リンク\n\n- [Vue.js - defineProps](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue 公式ドキュメント\n"
  },
  {
    "path": "book/online-book/src/ja/60-basic-sfc-compiler/030-define-emits.md",
    "content": "# defineEmits に対応する\n\n::: info この章について\nこの章では，`<script setup>` で使用する `defineEmits` マクロの実装方法を学びます．\\\n子コンポーネントから親コンポーネントへのイベント発行の仕組みを理解しましょう．\n:::\n\n## defineEmits とは\n\n`defineEmits` は `<script setup>` 内でコンポーネントが発行するイベントを宣言するためのコンパイラマクロです．\n\n```vue\n<script setup>\nconst emit = defineEmits(['change', 'update'])\n\nfunction handleClick() {\n  emit('change', 'new value')\n}\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"defineProps との違いは？\">\n\n`defineProps` は親から子へデータを渡す仕組み，\\\n`defineEmits` は子から親へイベントを通知する仕組みです．\\\nセットで覚えておきましょう！\n\n</KawaikoNote>\n\n## 実装の概要\n\ndefineEmits の処理は defineProps と非常に似ています：\n\n1. **マクロ呼び出しの検出**: AST から `defineEmits()` の呼び出しを見つける\n2. **引数の抽出**: イベント定義の配列またはオブジェクトを取得\n3. **コードの削除**: 元の `defineEmits()` 呼び出しを削除\n4. **オプションへの追加**: `emits` オプションとして出力に追加\n5. **emit 関数の提供**: setup のコンテキストから `emit` を取得\n\n## processDefineEmits 関数\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_EMITS = \"defineEmits\"\n\nlet emitsRuntimeDecl: Node | undefined\nlet emitIdentifier: string | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  // イベント定義を保存\n  emitsRuntimeDecl = node.arguments[0]\n\n  // 変数に代入されている場合は識別子を保存\n  // const emit = defineEmits(...) の \"emit\" 部分\n  if (declId) {\n    emitIdentifier =\n      declId.type === \"Identifier\"\n        ? declId.name\n        : scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST の走査\n\ndefineProps と同様に，`<script setup>` の本文を走査して `defineEmits` を検出します．\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr) || processDefineEmits(expr)) {\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        const isDefineProps = processDefineProps(init, declId)\n        const isDefineEmits = processDefineEmits(init, declId)\n        if (isDefineProps || isDefineEmits) {\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## emit 関数のセットアップ\n\n`defineEmits` で取得した emit 関数は，setup 関数の第 2 引数（SetupContext）から取得します．\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\n\nconst destructureElements: string[] = []\nif (emitIdentifier) {\n  destructureElements.push(\n    emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`\n  )\n}\n\nif (destructureElements.length) {\n  args += `, { ${destructureElements.join(\", \")} }`\n}\n```\n\nこれにより，以下のようなコードが生成されます：\n\n```ts\n// const emit = defineEmits(['change']) の場合\nsetup(__props, { emit }) {\n  // ...\n}\n\n// const emitFn = defineEmits(['change']) の場合\nsetup(__props, { emit: emitFn }) {\n  // ...\n}\n```\n\n## オプションへの追加\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  runtimeOptions += `\\n  props: ${...},`\n}\nif (emitsRuntimeDecl) {\n  runtimeOptions += `\\n  emits: ${scriptSetup.content\n    .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)\n    .trim()},`\n}\n```\n\n## 変換結果の例\n\n```vue\n<!-- 入力 -->\n<script setup>\nconst emit = defineEmits(['update', 'delete'])\n\nfunction handleUpdate(value) {\n  emit('update', value)\n}\n</script>\n\n<template>\n  <button @click=\"handleUpdate('new')\">Update</button>\n</template>\n```\n\n```ts\n// 出力\nexport default {\n  emits: ['update', 'delete'],\n  setup(__props, { emit }) {\n    function handleUpdate(value) {\n      emit('update', value)\n    }\n\n    return (_ctx) => {\n      return h('button', { onClick: _ctx.handleUpdate.bind(_ctx, 'new') }, 'Update')\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"defineProps と対称的！\">\n\n`defineEmits` の実装は `defineProps` とほぼ同じパターン：\n1. マクロ呼び出しを検出\n2. 引数を `emits` オプションに移動\n3. 変数があれば SetupContext から取得するよう変換\n\n覚えやすいですね！\n\n</KawaikoNote>\n\n## 動作確認\n\n子コンポーネント：\n\n```vue\n<script setup>\nconst props = defineProps({\n  modelValue: String\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nfunction updateValue(e) {\n  emit('update:modelValue', e.target.value)\n}\n</script>\n\n<template>\n  <input :value=\"modelValue\" @input=\"updateValue\" />\n</template>\n```\n\n親コンポーネント：\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\nimport CustomInput from './CustomInput.vue'\n\nconst text = ref('')\n</script>\n\n<template>\n  <CustomInput v-model=\"text\" />\n  <p>入力値: {{ text }}</p>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"実装完了！\">\n\ndefineEmits の実装が完了しました！\\\nこれで props と emits の両方のコンパイラマクロが使えるようになりました．\\\n次の章では scoped CSS の実装を学びます．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/030_define_emits)\n\n## まとめ\n\n- `defineEmits` は子から親へのイベント発行を宣言するマクロ\n- 処理パターンは `defineProps` と非常に類似\n- emit 関数は SetupContext から destructure して取得\n- `emits` オプションとしてコンポーネントに追加\n\n## 参考リンク\n\n- [Vue.js - defineEmits](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue 公式ドキュメント\n"
  },
  {
    "path": "book/online-book/src/ja/60-basic-sfc-compiler/040-scoped-css.md",
    "content": "# Scoped CSS に対応する\n\n::: info この章について\nこの章では，Vue の Scoped CSS 機能の実装方法を学びます．\\\nコンポーネントごとにスタイルを分離し，スタイルの衝突を防ぐ仕組みを理解しましょう．\n:::\n\n## Scoped CSS とは\n\nScoped CSS は，`<style scoped>` で定義されたスタイルをそのコンポーネントにのみ適用する機能です．\n\n```vue\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\nこのスタイルは，同じクラス名を持つ他のコンポーネントの要素には影響しません．\n\n<KawaikoNote variant=\"question\" title=\"なぜ Scoped CSS が必要？\">\n\n大規模なアプリケーションでは，異なるコンポーネントで同じクラス名を使うことがあります．\\\nScoped CSS がないと，スタイルが意図せず他のコンポーネントに影響してしまいます．\\\nコンポーネントごとにスタイルを分離することで，安全にスタイリングできます！\n\n</KawaikoNote>\n\n## 実装の仕組み\n\nScoped CSS は以下のステップで実現されます：\n\n1. **スコープ ID の生成**: コンポーネントごとにユニークな ID を生成\n2. **テンプレートの変換**: 要素に `data-v-xxx` 属性を追加\n3. **スタイルの変換**: セレクタに `[data-v-xxx]` を追加\n\n### 変換の例\n\n```vue\n<!-- 入力 -->\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\n```html\n<!-- 出力 (HTML) -->\n<p class=\"message\" data-v-7ba5bd90>Hello</p>\n\n<!-- 出力 (CSS) -->\n<style>\n.message[data-v-7ba5bd90] {\n  color: red;\n}\n</style>\n```\n\n## スコープ ID の生成\n\nコンポーネントごとにユニークな ID を生成します．通常はファイルパスのハッシュを使用します．\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nimport { createHash } from 'crypto'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  }\n\n  // スコープ ID を生成\n  descriptor.id = createHash('sha256')\n    .update(filename + source)\n    .digest('hex')\n    .slice(0, 8)\n\n  // ... 残りのパース処理\n}\n```\n\n## SFCStyleBlock の拡張\n\nスタイルブロックに scoped 情報を追加します．\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\"\n  scoped?: boolean  // 追加\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  // ...\n  node.props.forEach((p) => {\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      attrs[p.name] = p.value ? p.value.content || true : true\n      if (type === \"style\") {\n        if (p.name === \"scoped\") {\n          (block as SFCStyleBlock).scoped = true\n        }\n      }\n    }\n  })\n  return block\n}\n```\n\n## テンプレートの変換\n\nテンプレートコンパイル時に，要素に scopeId 属性を追加します．\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper, scopeId } = context\n  const { tag, props, children } = node\n\n  // scopeId がある場合は props に追加\n  let propsWithScope = props\n  if (scopeId) {\n    const scopeIdProp = `\"data-v-${scopeId}\": \"\"`\n    if (props) {\n      // 既存の props とマージ\n      propsWithScope = `{ ...${props}, ${scopeIdProp} }`\n    } else {\n      propsWithScope = `{ ${scopeIdProp} }`\n    }\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`)\n  genNodeList(genNullableArgs([tag, propsWithScope, children]), context)\n  push(`)`)\n}\n```\n\n## スタイルの変換\n\nCSS セレクタにスコープ属性セレクタを追加します．\n\n```ts\n// packages/compiler-sfc/src/compileStyle.ts\n\nimport postcss from 'postcss'\n\nexport interface SFCStyleCompileOptions {\n  source: string\n  filename: string\n  id: string\n  scoped?: boolean\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): string {\n  const { source, id, scoped } = options\n\n  if (!scoped) {\n    return source\n  }\n\n  // PostCSS を使ってセレクタを変換\n  const result = postcss([scopedPlugin(id)]).process(source, { from: undefined })\n  return result.css\n}\n\nfunction scopedPlugin(id: string) {\n  const scopeId = `data-v-${id}`\n\n  return {\n    postcssPlugin: 'vue-sfc-scoped',\n    Rule(rule) {\n      // セレクタに [data-v-xxx] を追加\n      rule.selectors = rule.selectors.map((selector) => {\n        return `${selector}[${scopeId}]`\n      })\n    },\n  }\n}\n```\n\n## Vite プラグインでの統合\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/main.ts\n\nasync function genStyleCode(descriptor: SFCDescriptor): Promise<string> {\n  let stylesCode = ``\n\n  for (let i = 0; i < descriptor.styles.length; i++) {\n    const style = descriptor.styles[i]\n    const src = descriptor.filename\n    const scoped = style.scoped ? '&scoped=true' : ''\n    const query = `?chibivue&type=style&index=${i}${scoped}&lang.css`\n    const styleRequest = src + query\n    stylesCode += `\\nimport ${JSON.stringify(styleRequest)}`\n  }\n\n  return stylesCode\n}\n\n// Vite プラグインの load でスタイルをコンパイル\nload(id) {\n  const { filename, query } = parseChibiVueRequest(id)\n  if (query.chibivue && query.type === \"style\") {\n    const descriptor = getDescriptor(filename, options)!\n    const style = descriptor.styles[query.index!]\n\n    if (query.scoped) {\n      return {\n        code: compileStyle({\n          source: style.content,\n          filename,\n          id: descriptor.id,\n          scoped: true,\n        })\n      }\n    }\n\n    return { code: style.content }\n  }\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"PostCSS の力！\">\n\nスタイルの変換には PostCSS を使っています．\\\nPostCSS は CSS を AST として扱えるツールで，セレクタの変換が簡単にできます．\\\nVue.js も内部で PostCSS を使っています！\n\n</KawaikoNote>\n\n## 動作確認\n\n```vue\n<!-- ComponentA.vue -->\n<template>\n  <p class=\"text\">Component A</p>\n</template>\n\n<style scoped>\n.text {\n  color: red;\n}\n</style>\n```\n\n```vue\n<!-- ComponentB.vue -->\n<template>\n  <p class=\"text\">Component B</p>\n</template>\n\n<style scoped>\n.text {\n  color: blue;\n}\n</style>\n```\n\n両方のコンポーネントが同じクラス名 `.text` を使用していますが，それぞれ異なる色で表示されます．\n\n## 特殊セレクタ\n\nScoped CSS では，いくつかの特殊なセレクタがサポートされています．\n\n### :deep() セレクタ\n\n子コンポーネントのスタイルを変更したい場合に使用します．\n\n```vue\n<style scoped>\n:deep(.child-class) {\n  color: blue;\n}\n</style>\n```\n\n変換後：\n\n```css\n[data-v-xxx] .child-class {\n  color: blue;\n}\n```\n\n### ::v-slotted() セレクタ\n\nスロットに渡されたコンテンツにスタイルを適用します．\n\n```vue\n<style scoped>\n::v-slotted(.slot-content) {\n  font-weight: bold;\n}\n</style>\n```\n\n変換後：\n\n```css\n.slot-content[data-v-xxx-s] {\n  font-weight: bold;\n}\n```\n\n`-s` サフィックスは「slotted（スロット）」を意味します．\nスロットコンテンツは親コンポーネントから渡されるため，\n通常のスコープ ID ではなく，特別なスロット用のスコープ ID が使われます．\n\n### :global() セレクタ\n\nグローバルスタイルを scoped スタイル内で定義します．\n\n```vue\n<style scoped>\n:global(.global-class) {\n  margin: 0;\n}\n</style>\n```\n\n変換後：\n\n```css\n.global-class {\n  margin: 0;\n}\n```\n\n## v-bind() による動的スタイル\n\nCSS 内でコンポーネントの状態を使用できます．\n\n```vue\n<script setup>\nimport { ref } from 'vue'\nconst color = ref('red')\n</script>\n\n<style scoped>\n.text {\n  color: v-bind(color);\n}\n</style>\n```\n\n変換後：\n\n```css\n.text[data-v-xxx] {\n  color: var(--xxx-color);\n}\n```\n\n`v-bind()` は CSS カスタムプロパティ（CSS 変数）に変換されます．\n実行時に，コンポーネントのインライン style として CSS 変数の値が設定されます．\n\n### 複雑な式の使用\n\nクォートで囲むことで，複雑な式も使用できます．\n\n```vue\n<style scoped>\n.box {\n  width: v-bind('size + \"px\"');\n  background: v-bind('theme.colors.primary');\n}\n</style>\n```\n\n<KawaikoNote variant=\"warning\" title=\"v-bind() のパフォーマンスについて\">\n\n`v-bind()` は便利な機能ですが，パフォーマンスに影響があります：\n\n- 各 `v-bind()` は CSS カスタムプロパティとしてインラインスタイルに設定されます\n- 値が変更されるたびにスタイルの再計算がトリガーされます\n- 頻繁に変更される値の場合，直接インラインスタイルを使用する方が効率的です\n\nアニメーションや頻繁な更新には，`v-bind()` よりもインラインスタイルや CSS アニメーションを検討してください．\n\n</KawaikoNote>\n\n## 今後の拡張\n\n以下の機能も検討できます：\n\n- **CSS Modules**: クラス名の自動生成\n- **CSS-in-JS との統合**: 動的スタイリングの強化\n\n<KawaikoNote variant=\"base\" title=\"実装に挑戦！\">\n\nこの章で説明した仕組みを参考に，ぜひ Scoped CSS を実装してみてください！\\\nPostCSS の使い方を学ぶ良い機会にもなります．\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/040_scoped_css)\n\n## まとめ\n\n- Scoped CSS はコンポーネントごとにスタイルを分離する機能\n- ユニークな scopeId を生成してテンプレートとスタイルに適用\n- テンプレートには `data-v-xxx` 属性，CSS には `[data-v-xxx]` セレクタ\n- PostCSS を使ってセレクタを変換\n\n## 参考リンク\n\n- [Vue.js - Scoped CSS](https://vuejs.org/api/sfc-css-features.html#scoped-css) - Vue 公式ドキュメント\n- [PostCSS](https://postcss.org/) - CSS 変換ツール\n"
  },
  {
    "path": "book/online-book/src/ja/60-basic-sfc-compiler/050-props-destructure.md",
    "content": "# Props の分割代入に対応する\n\n::: info この章について\nこの章では，Vue 3.5 で導入された Reactive Props Destructure 機能の実装方法を学びます．\\\nProps を分割代入しながらリアクティビティを維持する仕組みを理解しましょう．\n:::\n\n## Reactive Props Destructure とは\n\nVue 3.5 から，`<script setup>` 内で `defineProps` の戻り値を分割代入できるようになりました．\n\n```vue\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\nこの機能により，props へのアクセスがよりシンプルになります．\n\n<KawaikoNote variant=\"question\" title=\"なぜ特別な対応が必要？\">\n\n通常の JavaScript では，オブジェクトを分割代入すると値がコピーされ，元のオブジェクトとの接続が切れます．\\\nしかし Vue の props はリアクティブである必要があります．\\\nコンパイラが分割代入を `__props.xxx` へのアクセスに変換することで，リアクティビティを維持します！\n\n</KawaikoNote>\n\n## 実装の仕組み\n\nProps の分割代入は以下のステップで実現されます：\n\n1. **パターンの検出**: `const { ... } = defineProps(...)` を検出\n2. **バインディングの登録**: 分割代入された各プロパティを `PROPS` として登録\n3. **デフォルト値の処理**: デフォルト値を `withDefaults` 相当の処理に変換\n4. **コードの変換**: props アクセスを `__props.xxx` に変換\n\n### 変換の例\n\n```vue\n<!-- 入力 -->\n<script setup>\nconst { count, message = 'hello' } = defineProps({\n  count: Number,\n  message: String\n})\n\nconsole.log(count, message)\n</script>\n```\n\n```ts\n// 出力\nexport default {\n  props: {\n    count: Number,\n    message: { type: String, default: 'hello' }\n  },\n  setup(__props) {\n    console.log(__props.count, __props.message)\n\n    return (_ctx) => {\n      // ...\n    }\n  }\n}\n```\n\n## 分割代入パターンの検出\n\n`defineProps` の戻り値が `ObjectPattern`（分割代入パターン）に代入されているかを検出します．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\ninterface PropsDestructureBindings {\n  [key: string]: {\n    local: string      // ローカル変数名\n    default?: string   // デフォルト値\n  }\n}\n\nlet propsDestructuredBindings: PropsDestructureBindings = Object.create(null)\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  propsRuntimeDecl = node.arguments[0]\n\n  // 分割代入パターンの処理\n  if (declId && declId.type === \"ObjectPattern\") {\n    processPropsDestructure(declId)\n  } else if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## 分割代入の処理\n\n`ObjectPattern` から各プロパティを抽出し，バインディングとして登録します．\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"ObjectProperty\") {\n      const key = prop.key\n      const value = prop.value\n\n      // プロパティ名を取得\n      let propKey: string\n      if (key.type === \"Identifier\") {\n        propKey = key.name\n      } else if (key.type === \"StringLiteral\") {\n        propKey = key.value\n      } else {\n        continue\n      }\n\n      // ローカル変数名とデフォルト値を処理\n      let local: string\n      let defaultValue: string | undefined\n\n      if (value.type === \"Identifier\") {\n        // const { count } = defineProps(...)\n        local = value.name\n      } else if (value.type === \"AssignmentPattern\") {\n        // const { count = 0 } = defineProps(...)\n        if (value.left.type === \"Identifier\") {\n          local = value.left.name\n          defaultValue = scriptSetup!.content.slice(\n            value.right.start!,\n            value.right.end!\n          )\n        } else {\n          continue\n        }\n      } else {\n        continue\n      }\n\n      // バインディングを登録\n      propsDestructuredBindings[propKey] = { local, default: defaultValue }\n      bindingMetadata[local] = BindingTypes.PROPS\n    }\n  }\n}\n```\n\n## デフォルト値の処理\n\n分割代入でデフォルト値が指定された場合，props 定義にマージします．\n\n```ts\nfunction genRuntimeProps(): string | undefined {\n  if (!propsRuntimeDecl) return undefined\n\n  let propsString = scriptSetup!.content.slice(\n    propsRuntimeDecl.start!,\n    propsRuntimeDecl.end!\n  )\n\n  // デフォルト値がある場合はマージ\n  const defaults: Record<string, string> = {}\n  for (const key in propsDestructuredBindings) {\n    const binding = propsDestructuredBindings[key]\n    if (binding.default) {\n      defaults[key] = binding.default\n    }\n  }\n\n  if (Object.keys(defaults).length > 0) {\n    // withDefaults 相当の処理\n    propsString = mergeDefaults(propsString, defaults)\n  }\n\n  return propsString\n}\n\nfunction mergeDefaults(\n  propsString: string,\n  defaults: Record<string, string>\n): string {\n  // 実際の実装では AST を操作してデフォルト値をマージ\n  // ここでは簡略化した例\n  const ast = parseExpression(propsString)\n  // ... デフォルト値をマージする処理\n  return generate(ast).code\n}\n```\n\n## Props アクセスの変換\n\nテンプレートおよびスクリプト内で，分割代入された変数へのアクセスを `__props.xxx` に変換します．\n\n```ts\nfunction processPropsAccess(source: string): string {\n  const s = new MagicString(source)\n\n  // 識別子を走査して変換\n  walk(scriptSetupAst, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const binding = propsDestructuredBindings[node.name]\n        if (binding && binding.local === node.name) {\n          // props アクセスに変換\n          s.overwrite(node.start!, node.end!, `__props.${node.name}`)\n        }\n      }\n    }\n  })\n\n  return s.toString()\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"コンパイラの魔法！\">\n\n分割代入は通常 JavaScript の動作ではリアクティビティを失いますが，\\\nコンパイラが `__props.xxx` へのアクセスに変換することで，\\\nシンタックスシュガーとして分割代入の書き方を使えるようになります！\n\n</KawaikoNote>\n\n## Rest パターンの対応\n\n`...rest` パターンにも対応できます．\n\n```vue\n<script setup>\nconst { id, ...attrs } = defineProps(['id', 'class', 'style'])\n</script>\n```\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"RestElement\") {\n      // Rest パターンの処理\n      if (prop.argument.type === \"Identifier\") {\n        const restName = prop.argument.name\n        // rest は特別な処理が必要\n        // 実際には computed を使って残りの props を取得\n        bindingMetadata[restName] = BindingTypes.SETUP_REACTIVE_CONST\n      }\n    }\n    // ...\n  }\n}\n```\n\n## 動作確認\n\n```vue\n<!-- Parent.vue -->\n<script setup>\nimport { ref } from 'chibivue'\nimport Child from './Child.vue'\n\nconst count = ref(0)\nconst message = ref('Hello')\n</script>\n\n<template>\n  <Child :count=\"count\" :message=\"message\" />\n  <button @click=\"count++\">Increment</button>\n</template>\n```\n\n```vue\n<!-- Child.vue -->\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n\n// count と message は __props.count, __props.message に変換される\nconsole.log(count, message)\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\n## 今後の拡張\n\n以下の機能も検討できます：\n\n- **エイリアス対応**: `const { count: c } = defineProps(...)` の対応\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/050_props_destructure)\n\n## まとめ\n\n- Props Destructure は Vue 3.5 で導入された機能\n- 分割代入パターンを検出し，各プロパティを `PROPS` バインディングとして登録\n- デフォルト値は props 定義にマージ\n- 変数アクセスを `__props.xxx` に変換してリアクティビティを維持\n\n## 参考リンク\n\n- [Vue.js - Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure) - Vue 公式ドキュメント\n- [RFC - Reactive Props Destructure](https://github.com/vuejs/rfcs/discussions/502) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/ja/60-basic-sfc-compiler/060-type-based-macros.md",
    "content": "# 型ベースの defineProps / defineEmits\n\n::: info この章について\nこの章では，TypeScript の型引数を使った `defineProps` と `defineEmits` の実装方法を学びます．\\\n型定義からランタイム定義を生成する仕組みを理解しましょう．\n:::\n\n## 型ベースの宣言とは\n\nVue 3 では，`defineProps` と `defineEmits` を TypeScript のジェネリクスで宣言できます．\n\n```vue\n<script setup lang=\"ts\">\n// 型ベースの defineProps\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n\n// 型ベースの defineEmits\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"なぜ型ベースが便利？\">\n\nランタイム宣言では `Number`, `String` などを使いますが，\\\n型ベースなら TypeScript の型システムをそのまま使えます！\\\nIDE の補完やエラーチェックも強力になります．\n\n</KawaikoNote>\n\n## 実装の仕組み\n\n型ベースのマクロは以下のステップで処理されます：\n\n1. **型引数の検出**: `defineProps<T>()` のジェネリクスを検出\n2. **型の解析**: TypeScript の型定義を解析\n3. **ランタイム定義の生成**: 型からランタイム用の props/emits を生成\n4. **コードの出力**: 通常のランタイム宣言として出力\n\n### 変換の例\n\n```vue\n<!-- 入力 -->\n<script setup lang=\"ts\">\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n</script>\n```\n\n```ts\n// 出力\nexport default {\n  props: {\n    count: { type: Number, required: true },\n    message: { type: String, required: false }\n  },\n  setup(__props) {\n    // ...\n  }\n}\n```\n\n## 型引数の検出\n\n`defineProps` や `defineEmits` が型引数を持っているかを検出します．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nlet propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  // 型引数をチェック\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    if (typeArg) {\n      propsTypeDecl = resolveTypeElements(typeArg)\n    }\n  } else {\n    // ランタイム宣言\n    propsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n```\n\n## 型の解析\n\nTypeScript の型リテラルを解析して，プロパティ情報を抽出します．\n\n```ts\ninterface PropTypeData {\n  type: string[]      // 型の配列 (Union 対応)\n  required: boolean   // 必須かどうか\n}\n\nfunction extractPropsFromType(\n  typeDecl: TSTypeLiteral | TSInterfaceBody\n): Record<string, PropTypeData> {\n  const props: Record<string, PropTypeData> = {}\n\n  const members = typeDecl.type === \"TSTypeLiteral\"\n    ? typeDecl.members\n    : typeDecl.body\n\n  for (const member of members) {\n    if (member.type === \"TSPropertySignature\") {\n      const key = member.key\n      if (key.type !== \"Identifier\") continue\n\n      const propName = key.name\n      const isOptional = !!member.optional\n\n      // 型を解析\n      const types = member.typeAnnotation\n        ? resolveType(member.typeAnnotation.typeAnnotation)\n        : [\"null\"]\n\n      props[propName] = {\n        type: types,\n        required: !isOptional\n      }\n    }\n  }\n\n  return props\n}\n```\n\n## 型からコンストラクタへの変換\n\nTypeScript の型を JavaScript のコンストラクタに変換します．\n\n```ts\nfunction resolveType(node: TSType): string[] {\n  switch (node.type) {\n    case \"TSStringKeyword\":\n      return [\"String\"]\n\n    case \"TSNumberKeyword\":\n      return [\"Number\"]\n\n    case \"TSBooleanKeyword\":\n      return [\"Boolean\"]\n\n    case \"TSArrayType\":\n      return [\"Array\"]\n\n    case \"TSFunctionType\":\n      return [\"Function\"]\n\n    case \"TSObjectKeyword\":\n    case \"TSTypeLiteral\":\n      return [\"Object\"]\n\n    case \"TSUnionType\":\n      // Union 型は複数のコンストラクタを返す\n      const types: string[] = []\n      for (const t of node.types) {\n        // null/undefined は除外\n        if (t.type === \"TSNullKeyword\" || t.type === \"TSUndefinedKeyword\") {\n          continue\n        }\n        types.push(...resolveType(t))\n      }\n      return types\n\n    case \"TSTypeReference\":\n      // カスタム型や参照型\n      if (node.typeName.type === \"Identifier\") {\n        const name = node.typeName.name\n        // 組み込み型のマッピング\n        if (name === \"Array\") return [\"Array\"]\n        if (name === \"Function\") return [\"Function\"]\n        if (name === \"Object\") return [\"Object\"]\n        // その他はそのまま\n        return [name]\n      }\n      return [\"Object\"]\n\n    default:\n      return [\"null\"]\n  }\n}\n```\n\n## ランタイム定義の生成\n\n解析した型情報からランタイム用の props 定義を生成します．\n\n```ts\nfunction genRuntimePropsFromType(\n  propsDecl: Record<string, PropTypeData>\n): string {\n  const props: string[] = []\n\n  for (const [key, { type, required }] of Object.entries(propsDecl)) {\n    const typeStr = type.length === 1\n      ? type[0]\n      : `[${type.join(\", \")}]`\n\n    if (required) {\n      props.push(`${key}: { type: ${typeStr}, required: true }`)\n    } else {\n      props.push(`${key}: { type: ${typeStr}, required: false }`)\n    }\n  }\n\n  return `{ ${props.join(\", \")} }`\n}\n```\n\n## defineEmits の型処理\n\n`defineEmits` も同様に型引数を処理します．\n\n```ts\nlet emitsTypeDecl: TSFunctionType[] | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    emitsTypeDecl = resolveEmitsTypeElements(typeArg)\n  } else {\n    emitsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n\nfunction resolveEmitsTypeElements(\n  typeArg: TSType\n): TSFunctionType[] | undefined {\n  // 関数オーバーロード形式\n  if (typeArg.type === \"TSTypeLiteral\") {\n    return typeArg.members\n      .filter((m): m is TSCallSignatureDeclaration =>\n        m.type === \"TSCallSignatureDeclaration\"\n      )\n      .map(m => m as unknown as TSFunctionType)\n  }\n  return undefined\n}\n```\n\n## emits のランタイム定義生成\n\n```ts\nfunction genRuntimeEmitsFromType(\n  emitsDecl: TSFunctionType[]\n): string {\n  const events: string[] = []\n\n  for (const sig of emitsDecl) {\n    // 最初の引数がイベント名\n    const firstParam = sig.parameters?.[0]\n    if (firstParam?.type === \"Identifier\" && firstParam.typeAnnotation) {\n      const typeAnn = firstParam.typeAnnotation.typeAnnotation\n      if (typeAnn.type === \"TSLiteralType\" &&\n          typeAnn.literal.type === \"StringLiteral\") {\n        events.push(`\"${typeAnn.literal.value}\"`)\n      }\n    }\n  }\n\n  return `[${events.join(\", \")}]`\n}\n```\n\n### 変換例\n\n```vue\n<!-- 入力 -->\n<script setup lang=\"ts\">\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n```ts\n// 出力\nexport default {\n  emits: ['change', 'update'],\n  setup(__props, { emit }) {\n    // ...\n  }\n}\n```\n\n## withDefaults の対応\n\n型ベースの props でデフォルト値を指定するには `withDefaults` を使います．\n\n```vue\n<script setup lang=\"ts\">\ninterface Props {\n  count: number\n  message?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  message: 'default message'\n})\n</script>\n```\n\n```ts\nconst WITH_DEFAULTS = \"withDefaults\"\n\nfunction processWithDefaults(node: Node): boolean {\n  if (!isCallOf(node, WITH_DEFAULTS)) {\n    return false\n  }\n\n  const [propsCall, defaultsArg] = node.arguments\n\n  // defineProps を処理\n  if (isCallOf(propsCall, DEFINE_PROPS)) {\n    processDefineProps(propsCall)\n  }\n\n  // デフォルト値を保存\n  if (defaultsArg) {\n    propsDefaults = defaultsArg\n  }\n\n  return true\n}\n```\n\n## 動作確認\n\n```vue\n<!-- TypedComponent.vue -->\n<script setup lang=\"ts\">\ninterface Props {\n  id: number\n  name: string\n  active?: boolean\n}\n\ninterface Emits {\n  (e: 'select', id: number): void\n  (e: 'update', name: string): void\n}\n\nconst props = defineProps<Props>()\nconst emit = defineEmits<Emits>()\n\nfunction handleClick() {\n  emit('select', props.id)\n}\n</script>\n\n<template>\n  <div @click=\"handleClick\">\n    {{ name }} ({{ active ? 'active' : 'inactive' }})\n  </div>\n</template>\n```\n\n## 今後の拡張\n\n以下の機能も検討できます：\n\n- **インターフェース参照**: 別ファイルで定義した型の参照\n- **Mapped Types**: `Partial<T>` などの変換型\n- **Generic コンポーネント**: ジェネリック型パラメータを持つコンポーネント\n- **型のみのインポート**: `import type` の処理\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/060_type_based_macros)\n\n## まとめ\n\n- 型ベースの defineProps/defineEmits は TypeScript の型引数を使う\n- コンパイラが型を解析してランタイム定義を生成\n- TypeScript の型は JavaScript のコンストラクタにマッピング\n- withDefaults でデフォルト値を指定可能\n\n## 参考リンク\n\n- [Vue.js - TypeScript with Composition API](https://vuejs.org/guide/typescript/composition-api.html) - Vue 公式ドキュメント\n- [Vue.js - Type-only props/emit declarations](https://vuejs.org/api/sfc-script-setup.html#type-only-props-emit-declarations) - Vue 公式ドキュメント\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/010-plugins/010-router.md",
    "content": "# Router\n\n## ルーターとは\n\nシングルページアプリケーション（SPA）では，URL に応じて異なるコンポーネントを表示する必要があります．Vue.js のエコシステムでは，Vue Router がこの機能を提供しています．\n\n<KawaikoNote variant=\"question\" title=\"SPAのルーティングって？\">\n\n従来の Web サイトでは，URL が変わるたびにサーバーから新しい HTML を取得していました．\nSPA では，JavaScript でページの切り替えを行い，サーバーへのリクエストなしに画面を更新します．\nこれを「クライアントサイドルーティング」と呼びます．\n\n</KawaikoNote>\n\nこの章では，Vue Router の基本的な機能を chibivue-router として実装します．\n\n## パッケージ構成\n\nchibivue-router は `@extensions/chibivue-router` パッケージで提供されています．\n\n```\n@extensions/chibivue-router/src/\n├── index.ts              # エクスポート\n├── router.ts             # メインのルーターロジック\n├── history.ts            # History API ラッパー\n├── RouterView.ts         # RouterView コンポーネント\n├── useApi.ts             # Composition API フック\n├── injectionSymbols.ts   # Dependency Injection のキー\n└── types/\n    └── index.ts          # 型定義\n```\n\n## 型定義\n\n### RouteLocationNormalizedLoaded\n\n現在のルート情報を表す型です．\n\n```ts\n// types/index.ts\nexport interface RouteLocationNormalizedLoaded {\n  fullPath: string;\n  component: any;\n}\n```\n\n### RouteRecord\n\nルート定義を表す型です．\n\n```ts\n// router.ts\nexport interface RouteRecord {\n  path: string;\n  component: any;\n}\n```\n\n### Router インターフェース\n\nルーターの公開 API を定義します．\n\n```ts\n// router.ts\nexport interface Router {\n  install(app: App): void;\n  push(to: string): void;\n  replace(to: string): void;\n}\n```\n\n## History API の抽象化\n\nブラウザの History API をラップして，ルーターから使いやすくします．\n\n### RouterHistory インターフェース\n\n```ts\n// history.ts\nexport interface RouterHistory {\n  location: Location;\n  push(to: string): void;\n  replace(to: string): void;\n  go(delta: number, triggerListeners?: boolean): void;\n}\n```\n\n### createWebHistory 関数\n\n```ts\n// history.ts\nexport const createWebHistory = (): RouterHistory => {\n  return {\n    location: window.location,\n    push(to: string) {\n      window.history.pushState({}, \"\", to);\n    },\n    replace(to: string) {\n      window.history.replaceState({}, \"\", to);\n    },\n    go(delta: number, triggerListeners?: boolean) {\n      window.history.go(delta);\n    },\n  };\n};\n```\n\nポイント：\n- `pushState`: 履歴に新しいエントリを追加（戻るボタンで前のページに戻れる）\n- `replaceState`: 現在の履歴エントリを置き換え（履歴には残らない）\n- `go`: 履歴を前後に移動\n\n<KawaikoNote variant=\"funny\" title=\"pushState vs replaceState\">\n\n`pushState` は「本棚に新しい本を追加する」イメージ．\n`replaceState` は「今読んでいる本を別の本に交換する」イメージ．\n戻るボタンは「前に読んでいた本に戻る」ことに相当します．\n\n</KawaikoNote>\n\n## Dependency Injection のキー\n\nルーター関連の値を provide/inject で共有するためのキーを定義します．\n\n```ts\n// injectionSymbols.ts\nimport type { ComputedRef, InjectionKey, Ref } from \"@chibivue/runtime-core\";\nimport type { Router } from \"./router\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\n\n// ルーター本体\nexport const routerKey = Symbol() as InjectionKey<Router>;\n\n// 現在のルート（computed でラップ）\nexport const routeLocationKey = Symbol() as InjectionKey<\n  ComputedRef<RouteLocationNormalizedLoaded>\n>;\n\n// RouterView 用のルート（Ref）\nexport const routerViewLocationKey = Symbol() as InjectionKey<\n  Ref<RouteLocationNormalizedLoaded>\n>;\n```\n\n3 つのキーを分けている理由：\n1. `routerKey`: ナビゲーションメソッド（`push`, `replace`）へのアクセス用\n2. `routeLocationKey`: `useRoute()` で現在のルート情報を取得する用（computed でリアクティブ）\n3. `routerViewLocationKey`: `RouterView` コンポーネントが表示するコンポーネントを決定する用\n\n## createRouter の実装\n\n### ルート解決\n\n```ts\n// router.ts\nconst resolve = (to: string) => {\n  const route = options.routes.find((route) => route.path === to);\n  return {\n    fullPath: to,\n    component: route?.component ?? null,\n  };\n};\n```\n\n現在の実装では完全一致のみをサポートしています．Vue Router の本家実装ではパラメータ（`/user/:id`）や正規表現にも対応しています．\n\n### 状態管理\n\n```ts\n// router.ts\nconst currentRoute = ref<RouteLocationNormalizedLoaded>({\n  fullPath: routerHistory.location.pathname,\n  component: resolve(routerHistory.location.pathname).component,\n});\n```\n\n現在のルート情報を `ref` で管理します．これにより，ルートが変わると `RouterView` が自動的に再レンダリングされます．\n\n### ナビゲーションメソッド\n\n```ts\n// router.ts\nfunction push(to: string) {\n  routerHistory.push(to);\n  currentRoute.value = resolve(to);\n}\n\nfunction replace(to: string) {\n  routerHistory.replace(to);\n  currentRoute.value = resolve(to);\n}\n```\n\nURL を変更し，同時にリアクティブな状態も更新します．\n\n### プラグインのインストール\n\n```ts\n// router.ts\ninstall(app: App) {\n  const router = this;\n\n  // RouterView コンポーネントをグローバル登録\n  app.component(\"RouterView\", RouterViewImpl);\n\n  // リアクティブなルート情報を作成\n  const reactiveRoute = computed(() => currentRoute.value);\n\n  // 値を provide\n  app.provide(routerKey, router);\n  app.provide(routeLocationKey, reactive(reactiveRoute));\n  app.provide(routerViewLocationKey, currentRoute);\n}\n```\n\n`app.use(router)` を呼び出すと，この `install` メソッドが実行されます．\n\n## RouterView コンポーネント\n\n現在のルートに対応するコンポーネントを表示します．\n\n```ts\n// RouterView.ts\nimport { type ComponentOptions, Fragment, h, inject } from \"chibivue\";\nimport { routerViewLocationKey } from \"./injectionSymbols\";\n\nexport const RouterViewImpl: ComponentOptions = {\n  name: \"RouterView\",\n  setup() {\n    const injectedRoute = inject(routerViewLocationKey)!;\n\n    return () => {\n      const ViewComponent = injectedRoute.value.component;\n\n      // Fragment でラップしてレンダリング\n      const component = h(Fragment, [\n        h(ViewComponent, { key: injectedRoute.value.fullPath }),\n      ]);\n\n      return component;\n    };\n  },\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"key 属性が重要！\">\n\n`key` に `fullPath` を指定することで，ルートが変わるたびにコンポーネントが完全に再マウントされます．\nこれがないと，同じコンポーネントが使い回され，`setup` が再実行されません．\n\n</KawaikoNote>\n\nFragment でラップしている理由は，patch children の動作を正しく行うためです．\n\n## Composition API フック\n\n### useRouter\n\nルーターインスタンスを取得します．\n\n```ts\n// useApi.ts\nexport function useRouter(): Router {\n  return inject(routerKey)!;\n}\n```\n\n使用例：\n```ts\nconst router = useRouter()\nrouter.push('/about')\n```\n\n### useRoute\n\n現在のルート情報を取得します．\n\n```ts\n// useApi.ts\nexport function useRoute(): ComputedRef<RouteLocationNormalizedLoaded> {\n  return inject(routeLocationKey)!;\n}\n```\n\n使用例：\n```ts\nconst route = useRoute()\nconsole.log(route.value.fullPath) // '/about'\n```\n\n## 使用例\n\n### ルーターの設定\n\n```ts\n// router.ts\nimport { createRouter, createWebHistory } from 'chibivue-router'\nimport Home from './pages/Home.vue'\nimport About from './pages/About.vue'\nimport Contact from './pages/Contact.vue'\n\nexport const router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '/', component: Home },\n    { path: '/about', component: About },\n    { path: '/contact', component: Contact },\n  ],\n})\n```\n\n### アプリケーションへの登録\n\n```ts\n// main.ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\nimport { router } from './router'\n\nconst app = createApp(App)\napp.use(router)\napp.mount('#app')\n```\n\n### テンプレートでの使用\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { useRouter } from 'chibivue-router'\n\nconst router = useRouter()\n</script>\n\n<template>\n  <header>\n    <nav>\n      <button @click=\"router.push('/')\">Home</button>\n      <button @click=\"router.push('/about')\">About</button>\n      <button @click=\"router.push('/contact')\">Contact</button>\n    </nav>\n  </header>\n\n  <main>\n    <RouterView />\n  </main>\n</template>\n```\n\n## 処理フロー\n\n```\napp.use(router)\n  ↓\nrouter.install(app)\n  ├── app.component(\"RouterView\", RouterViewImpl)\n  ├── app.provide(routerKey, router)\n  ├── app.provide(routeLocationKey, ...)\n  └── app.provide(routerViewLocationKey, currentRoute)\n  ↓\nRouterView がレンダリング\n  ↓\ninject(routerViewLocationKey) で currentRoute を取得\n  ↓\ncurrentRoute.value.component をレンダリング\n\n--- ナビゲーション ---\n\nrouter.push('/about')\n  ↓\nrouterHistory.push('/about')  ← URL 変更\n  ↓\ncurrentRoute.value = resolve('/about')  ← 状態更新\n  ↓\nRouterView が再レンダリング\n  ↓\n新しいコンポーネントを表示\n```\n\n## 今後の拡張\n\n現在の実装は最小限ですが，Vue Router には以下のような機能があります：\n\n1. **RouterLink コンポーネント**: `<a>` タグをラップしたナビゲーション用コンポーネント\n2. **ルートパラメータ**: `/user/:id` のような動的セグメント\n3. **クエリパラメータ**: `?key=value` の解析\n4. **ナビゲーションガード**: `beforeEach`, `afterEach` などのフック\n5. **popstate イベント**: ブラウザの戻る/進むボタンへの対応\n6. **ネストされたルート**: 子ルートの定義\n\n<KawaikoNote variant=\"surprise\" title=\"実装完了！\">\n\nこれでシンプルなルーターが完成しました．\n約 100 行のコードで SPA のルーティングを実現できています．\nVue Router の仕組みを理解する良い出発点になったでしょう．\n\n</KawaikoNote>\n\n## まとめ\n\nchibivue-router の実装は以下の要素で構成されています：\n\n1. **History API のラップ**: `createWebHistory` でブラウザの履歴操作を抽象化\n2. **リアクティブな状態管理**: `ref` で現在のルートを管理\n3. **Dependency Injection**: `provide/inject` でルーター情報をコンポーネントツリー全体に共有\n4. **RouterView コンポーネント**: 現在のルートに対応するコンポーネントを動的に表示\n5. **Composition API フック**: `useRouter` と `useRoute` で簡単にアクセス\n\nVue のプラグインシステム，provide/inject，リアクティビティシステムを組み合わせることで，クライアントサイドルーティングを実現しています．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/010-plugins/020-preprocessors.md",
    "content": "# CSS プリプロセッサ\n\n## プリプロセッサとは\n\nCSS プリプロセッサは，CSS を拡張した言語（SCSS，Less，Stylus など）を標準の CSS に変換するツールです．これらの言語は変数，ネスト，ミックスイン，関数などの機能を提供し，CSS の記述をより効率的にします．\n\n<KawaikoNote variant=\"question\" title=\"なぜプリプロセッサを使うの？\">\n\nプレーンな CSS にはいくつかの制限があります：\n- 変数がない（CSS カスタムプロパティは後から追加されましたが）\n- ネストができない\n- コードの再利用が難しい\n\nプリプロセッサはこれらの問題を解決し，保守性の高いスタイルシートを書けるようにします．\n\n</KawaikoNote>\n\nVue SFC では，`<style>` ブロックに `lang` 属性を指定することでプリプロセッサを使用できます．\n\n```vue\n<style lang=\"scss\">\n$primary-color: #42b883;\n\n.container {\n  .title {\n    color: $primary-color;\n  }\n}\n</style>\n```\n\n## サポートされているプリプロセッサ\n\nVue/chibivue では以下のプリプロセッサをサポートしています：\n\n| プリプロセッサ | lang 属性 | 特徴 |\n|--------------|----------|------|\n| **SCSS** | `scss` | CSS に近い構文，変数，ネスト，ミックスイン |\n| **Sass** | `sass` | インデントベースの構文（波括弧なし） |\n| **Less** | `less` | 変数（`@`），ミックスイン，関数 |\n| **Stylus** | `styl`, `stylus` | 柔軟な構文，オプションの区切り文字 |\n\n## 型定義\n\n### StylePreprocessor\n\nプリプロセッサの共通インターフェースです．\n\n```ts\n// style/preprocessors.ts\nexport type StylePreprocessor = (\n  source: string,\n  map: RawSourceMap | undefined,\n  options: {\n    [key: string]: any;\n    additionalData?: string | ((source: string, filename: string) => string);\n    filename: string;\n  },\n  customRequire: (id: string) => any,\n) => StylePreprocessorResults;\n```\n\n### StylePreprocessorResults\n\nプリプロセッサの処理結果を表す型です．\n\n```ts\nexport interface StylePreprocessorResults {\n  code: string;           // 変換後の CSS\n  map?: object;          // ソースマップ\n  errors: Error[];       // エラー一覧\n  dependencies: string[]; // 依存ファイル（@import など）\n}\n```\n\n`dependencies` は重要です．プリプロセッサで `@import` したファイルが変更された際に，Vite などのツールが再ビルドをトリガーできるようになります．\n\n## 処理フロー\n\n```\nSFC ファイル (.vue)\n    ↓\n[SFC Parser] - <style lang=\"scss\"> を検出\n    ↓\n[compileStyle]\n    ↓\n1. プリプロセッサの選択\n   processors[preprocessLang] → scss プリプロセッサ\n    ↓\n2. プリプロセッサで変換\n   SCSS/Sass/Less/Stylus → CSS\n    ↓\n3. PostCSS パイプライン\n   ├── cssVarsPlugin (v-bind 処理)\n   ├── trimPlugin (空白削除)\n   └── scopedPlugin (scoped CSS)\n    ↓\n4. 結果を返す\n   { code, map, errors, dependencies }\n```\n\n## プリプロセッサの実装\n\n### SCSS プリプロセッサ\n\n```ts\n// style/preprocessors.ts\nconst scss: StylePreprocessor = (source, map, options, load = require) => {\n  // Dart Sass ライブラリを動的にロード\n  const nodeSass: typeof import(\"sass\") = load(\"sass\");\n  const { compileString, renderSync } = nodeSass;\n\n  // additionalData の適用（共通変数の注入など）\n  const data = getSource(source, options.filename, options.additionalData);\n\n  let css: string;\n  let dependencies: string[];\n  let sourceMap: any;\n\n  try {\n    if (compileString) {\n      // 新しい API（Sass 1.55.0 以降）\n      const result = compileString(data, {\n        ...options,\n        url: pathToFileURL(options.filename),\n        sourceMap: !!map,\n      });\n      css = result.css;\n      dependencies = result.loadedUrls.map((url) => fileURLToPath(url));\n      sourceMap = map ? result.sourceMap! : undefined;\n    } else {\n      // 旧 API（後方互換性）\n      const result = renderSync({\n        ...options,\n        data,\n        file: options.filename,\n        outFile: options.filename,\n        sourceMap: !!map,\n      });\n      css = result.css.toString();\n      dependencies = result.stats.includedFiles;\n      sourceMap = map ? JSON.parse(result.map!.toString()) : undefined;\n    }\n\n    // ソースマップのマージ\n    if (map) {\n      return {\n        code: css,\n        errors: [],\n        dependencies,\n        map: merge(map, sourceMap!),\n      };\n    }\n    return { code: css, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"API の互換性\">\n\nSass には新旧 2 つの API があります．\n`compileString` は新しい API で，`renderSync` は旧 API です．\n両方に対応することで，どのバージョンの Sass でも動作します．\n\n</KawaikoNote>\n\n### Sass プリプロセッサ\n\nSass は SCSS と同じエンジンを使用しますが，インデントベースの構文を使用します．\n\n```ts\nconst sass: StylePreprocessor = (source, map, options, load) =>\n  scss(\n    source,\n    map,\n    {\n      ...options,\n      indentedSyntax: true,  // インデント構文を有効化\n    },\n    load,\n  );\n```\n\n### Less プリプロセッサ\n\n```ts\nconst less: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeLess = load(\"less\");\n\n  let result: any;\n  let error: Error | null = null;\n\n  // Less の render は非同期だが，syncImport: true で同期的に実行\n  nodeLess.render(\n    getSource(source, options.filename, options.additionalData),\n    { ...options, syncImport: true },\n    (err: Error | null, output: any) => {\n      error = err;\n      result = output;\n    },\n  );\n\n  if (error) return { code: \"\", errors: [error], dependencies: [] };\n\n  // Less は imports プロパティで依存ファイルを返す\n  const dependencies = result.imports;\n\n  if (map) {\n    return {\n      code: result.css.toString(),\n      map: merge(map, result.map),\n      errors: [],\n      dependencies,\n    };\n  }\n\n  return {\n    code: result.css.toString(),\n    errors: [],\n    dependencies,\n  };\n};\n```\n\n### Stylus プリプロセッサ\n\n```ts\nconst styl: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeStylus = load(\"stylus\");\n\n  try {\n    const ref = nodeStylus(source, options);\n\n    // ソースマップの設定\n    if (map) ref.set(\"sourcemap\", { inline: false, comment: false });\n\n    const result = ref.render();\n    const dependencies = ref.deps();  // 依存ファイルを取得\n\n    if (map) {\n      return {\n        code: result,\n        map: merge(map, ref.sourcemap),\n        errors: [],\n        dependencies,\n      };\n    }\n\n    return { code: result, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n## additionalData による共通スタイルの注入\n\n`additionalData` オプションを使用すると，すべてのスタイルファイルに共通のコードを注入できます．\n\n```ts\nfunction getSource(\n  source: string,\n  filename: string,\n  additionalData?: string | ((source: string, filename: string) => string),\n) {\n  if (!additionalData) return source;\n\n  // 関数の場合は動的に生成\n  if (isFunction(additionalData)) {\n    return additionalData(source, filename);\n  }\n\n  // 文字列の場合はソースの先頭に追加\n  return additionalData + source;\n}\n```\n\n使用例（Vite 設定）：\n\n```ts\n// vite.config.ts\nexport default defineConfig({\n  css: {\n    preprocessorOptions: {\n      scss: {\n        // 全ての SCSS ファイルに変数を注入\n        additionalData: `@import \"@/styles/variables.scss\";`,\n      },\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"funny\" title=\"グローバル変数の注入\">\n\n`additionalData` は「すべてのスタイルファイルの先頭に自動でコピペする」機能です．\n変数やミックスインを毎回 import する手間が省けます．\n\n</KawaikoNote>\n\n## プリプロセッサの登録\n\n```ts\nexport type PreprocessLang = \"less\" | \"sass\" | \"scss\" | \"styl\" | \"stylus\";\n\nexport const processors: Record<PreprocessLang, StylePreprocessor> = {\n  less,\n  sass,\n  scss,\n  styl,\n  stylus: styl,  // エイリアス\n};\n```\n\n## compileStyle での統合\n\nプリプロセッサは `compileStyle` 関数の中で呼び出されます．\n\n```ts\n// compileStyle.ts\nexport function doCompileStyle(options: SFCAsyncStyleCompileOptions) {\n  const {\n    filename,\n    id,\n    scoped = false,\n    trim = true,\n    preprocessLang,\n    // ...\n  } = options;\n\n  // プリプロセッサの選択\n  const preprocessor = preprocessLang && processors[preprocessLang];\n\n  // プリプロセッサがあれば実行\n  const preProcessedSource = preprocessor && preprocess(options, preprocessor);\n\n  // ソースマップの取得（プリプロセッサからまたは入力から）\n  const map = preProcessedSource ? preProcessedSource.map : options.inMap;\n\n  // CSS ソース（変換後または元のまま）\n  const source = preProcessedSource ? preProcessedSource.code : options.source;\n\n  // PostCSS パイプラインを構築\n  const plugins = (postcssPlugins || []).slice();\n  plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));\n  if (trim) plugins.push(trimPlugin());\n  if (scoped) plugins.push(scopedPlugin(longId));\n\n  // 依存ファイルの収集\n  const dependencies = new Set(\n    preProcessedSource ? preProcessedSource.dependencies : []\n  );\n\n  // PostCSS で処理\n  const result = postcss(plugins).process(source, postCSSOptions);\n\n  return {\n    code: result.css,\n    map: result.map?.toJSON(),\n    errors: [...errors],\n    dependencies,\n  };\n}\n```\n\n## 使用例\n\n### SCSS\n\n```vue\n<style lang=\"scss\">\n$primary: #42b883;\n$secondary: #35495e;\n\n.card {\n  background: $secondary;\n\n  .title {\n    color: $primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px rgba($secondary, 0.3);\n  }\n}\n</style>\n```\n\n### Less\n\n```vue\n<style lang=\"less\">\n@primary: #42b883;\n@secondary: #35495e;\n\n.card {\n  background: @secondary;\n\n  .title {\n    color: @primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px fade(@secondary, 30%);\n  }\n}\n</style>\n```\n\n### Stylus\n\n```vue\n<style lang=\"stylus\">\nprimary = #42b883\nsecondary = #35495e\n\n.card\n  background secondary\n\n  .title\n    color primary\n    font-size 1.5rem\n\n  &:hover\n    box-shadow 0 4px 8px rgba(secondary, 0.3)\n</style>\n```\n\n## ソースマップの連鎖\n\nプリプロセッサと PostCSS の両方がソースマップを生成します．これらを正しく連結するために `merge-source-map` ライブラリを使用しています．\n\n```\nSCSS ソース\n    ↓ [SCSS → CSS]\n    ↓ ソースマップ A\nCSS\n    ↓ [PostCSS]\n    ↓ ソースマップ B\n最終 CSS\n    ↓\nmerge(A, B) → 最終ソースマップ\n```\n\nこれにより，ブラウザの DevTools でデバッグする際に，元の SCSS/Less/Stylus ファイルの行番号が表示されます．\n\n<KawaikoNote variant=\"surprise\" title=\"デバッグが楽になる！\">\n\nソースマップがあれば，ブラウザで「この CSS どこから来たの？」と思ったときに，\n変換前の SCSS ファイルの正確な位置がわかります．\n\n</KawaikoNote>\n\n## まとめ\n\nCSS プリプロセッサの実装は以下の要素で構成されています：\n\n1. **共通インターフェース**: `StylePreprocessor` 型で各プリプロセッサを抽象化\n2. **動的ロード**: `require()` または `customRequire` でプリプロセッサをロード\n3. **additionalData**: 共通スタイル（変数，ミックスインなど）の注入\n4. **依存ファイル追跡**: `@import` したファイルを収集してホットリロードに対応\n5. **ソースマップの連鎖**: プリプロセッサと PostCSS のソースマップをマージ\n6. **PostCSS との統合**: プリプロセッサの出力を PostCSS パイプラインに渡す\n\nVue/chibivue の SFC コンパイラは，プリプロセッサを抽象化することで，ユーザーが好みの CSS 言語を使えるようにしています．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/010-plugins/020-store.md",
    "content": "# Store\n\n## ストアとは\n\nアプリケーションが大きくなると，複数のコンポーネント間で状態を共有する必要が出てきます．Vue.js のエコシステムでは，Pinia がこの機能を提供しています．\n\nこの章では，Pinia の基本的な機能を chibivue-store として実装します．\n\n### なぜライブラリが必要なのか\n\n複数のコンポーネント間で状態を共有するだけなら，モジュールスコープで `ref` や `computed` をエクスポートすれば十分です：\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\n\nexport const count = ref(0);\nexport const doubleCount = computed(() => count.value * 2);\nexport const increment = () => count.value++;\n```\n\nCSR（Client-Side Rendering）ではこれで問題ありません．しかし，SSR（Server-Side Rendering）では重大な問題が発生します．\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\nSSR では「**Cross-Request State Pollution**（リクエスト間状態汚染）」に注意が必要です．\n\nサーバーはモジュールを一度だけ初期化するため，上記のようなモジュールスコープの状態は**すべてのリクエスト間で共有**されてしまいます．\nこれにより，あるユーザーの状態が別のユーザーに漏洩する危険性があります．\n\n</KawaikoNote>\n\nPinia のような状態管理ライブラリを使うと，setup 内で `useXxxStore()` を呼ぶだけで，ライブラリが自動的にリクエストごとの状態分離を行ってくれます．\n\n<KawaikoNote variant=\"info\" title=\"Nuxt を使っている場合\">\n\nNuxt を使っている場合は，[useState](https://nuxt.com/docs/api/composables/use-state) という SSR フレンドリーな状態管理のコンポーザブルが提供されています．\nシンプルな状態共有であれば，Pinia を導入せずに `useState` で十分な場合もあります．\n\n</KawaikoNote>\n\nこの章では，CSR での基本的な使い方から，SSR でのハイドレーションまでを説明します．\n\nSSR の詳細については [SSR の章](/ja/90-web-application-essentials/020-ssr/010-create-ssr-app) を参照してください．\n\n## パッケージ構成\n\nchibivue-store は `@extensions/chibivue-store` パッケージで提供されています．\n\n```\n@extensions/chibivue-store/src/\n├── index.ts           # エクスポート\n├── createStore.ts     # ルートストアの作成\n├── rootStore.ts       # ストアのインターフェースとシンボル\n└── store.ts           # defineStore の実装\n```\n\n## 型定義\n\n### StateTree\n\nストアが保持する状態の型です．\n\n```ts\n// rootStore.ts\nexport type StateTree = Record<string | number | symbol, any>;\n```\n\n### Store インターフェース\n\nルートストアの公開 API を定義します．\n\n```ts\n// rootStore.ts\nexport interface Store {\n  install: (app: App) => void;\n  use(plugin: StorePlugin): Store;\n  state: Ref<Record<string, StateTree>>;\n  _p: StorePlugin[];\n  _a: App | null;\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n- `install`: Vue プラグインとしてのインストールメソッド\n- `use`: プラグインを追加するメソッド\n- `state`: すべてのストアの状態を保持する ref（SSR 用）\n- `_p`: インストールされたプラグイン\n- `_a`: このストアにリンクされた App\n- `_e`: ストアがアタッチされた EffectScope\n- `_s`: 定義されたストアを ID で管理する Map\n\n### StoreInstance インターフェース\n\n各ストアインスタンスが持つメソッドを定義します．\n\n```ts\n// store.ts\nexport interface StoreInstance<\n  Id extends string = string,\n  S extends StateTree = StateTree,\n  G extends _GettersTree<S> = _GettersTree<S>,\n  A = Record<string, (...args: any[]) => any>,\n> {\n  $id: Id;\n  $state: S;\n  $patch: (partialState: Partial<S> | ((state: S) => void)) => void;\n  $reset: () => void;\n}\n```\n\n- `$id`: ストアの識別子\n- `$state`: ストアの状態（Options API スタイルのみ）\n- `$patch`: 状態の一括更新\n- `$reset`: 状態の初期値へのリセット（Options API スタイルのみ）\n\n## Dependency Injection のキー\n\nストアを provide/inject で共有するためのキーを定義します．\n\n```ts\n// rootStore.ts\nimport type { InjectionKey } from \"chibivue\";\n\nexport const storeSymbol: InjectionKey<Store> = Symbol();\n```\n\nこのシンボルを使って，`createStore()` で作成したストアをアプリ全体に provide します．\n\n## createStore の実装\n\nルートストアを作成する関数です．\n\n```ts\n// createStore.ts\nimport { effectScope, markRaw, ref } from \"chibivue\";\nimport { type Store, setActiveStore, storeSymbol } from \"./rootStore\";\n\nexport function createStore(): Store {\n  const scope = effectScope();\n\n  const state = scope.run(() => ref({}))!;\n\n  let _p: StorePlugin[] = [];\n  let toBeInstalled: StorePlugin[] = [];\n\n  const store: Store = markRaw({\n    install(app) {\n      setActiveStore(store);\n      store._a = app;\n      app.provide(storeSymbol, store);\n      toBeInstalled.forEach((plugin) => _p.push(plugin));\n      toBeInstalled = [];\n    },\n\n    use(plugin) {\n      if (!this._a) {\n        toBeInstalled.push(plugin);\n      } else {\n        _p.push(plugin);\n      }\n      return this;\n    },\n\n    _p,\n    _a: null,\n    _e: scope,\n    _s: new Map(),\n    state,\n  });\n\n  return store;\n}\n```\n\nポイント：\n- `effectScope()` で detached スコープを作成し，ストアのライフサイクルを管理\n- `state` は `ref({})` で，すべてのストアの状態を一元管理（SSR 用）\n- `markRaw` でストアオブジェクト自体をリアクティブ化から除外\n- `install` メソッドで `app.provide` を呼び出し，ストアをアプリ全体で利用可能にする\n\n### activeStore の管理\n\n```ts\n// rootStore.ts\nexport let activeStore: Store | undefined;\nexport const setActiveStore = (store: Store | undefined): Store | undefined =>\n  (activeStore = store);\n\nexport const getActiveStore = (): Store | undefined => {\n  const store = hasInjectionContext() && inject(storeSymbol, null);\n\n  if (__DEV__ && !store && typeof window === \"undefined\") {\n    console.warn(\n      `[chibivue-store]: Store instance not found in context. ` +\n      `This falls back to the global activeStore which exposes you to ` +\n      `cross-request state pollution on the server.`,\n    );\n  }\n\n  return store || activeStore;\n};\n```\n\n`activeStore` は，コンポーネント外からストアにアクセスする場合（例：他のストア内）に使用されます．\n\n`getActiveStore` は `hasInjectionContext()` を使って injection context を確認し，SSR 環境で context がない場合は警告を出します．これにより，Cross-Request State Pollution のリスクを開発者に知らせます．\n\n## defineStore の実装\n\n個別のストアを定義する関数です．Pinia と同様に，2 つのスタイルで定義できます．\n\n### Composition API スタイル\n\n```ts\n// Composition API style (setup function)\nexport function defineStore<Id extends string, SS extends StateTree>(\n  id: Id,\n  setup: () => SS,\n): () => SS;\n```\n\n`setup` 関数を渡して，`ref` や `computed` を使って状態を定義します．\n\n### Options API スタイル\n\n```ts\n// Options API style\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(options: StoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>;\n\n// Options API style (id as first argument)\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(\n  id: Id,\n  options: Omit<StoreOptions<Id, S, G, A>, \"id\">,\n): StoreDefinition<Id, S, G, A>;\n```\n\n`state`, `getters`, `actions` を持つオブジェクトで定義します．\n\n### StoreOptions インターフェース\n\n```ts\ninterface StoreOptions<Id extends string, S extends StateTree, G extends _GettersTree<S>, A> {\n  id: Id;\n  state?: () => S;\n  getters?: G & ThisType<S & { [K in keyof G]: ReturnType<G[K]> }>;\n  actions?: A & ThisType<S & A & { [K in keyof G]: ReturnType<G[K]> }>;\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"ThisType の魔法\">\n\n`ThisType` を使うことで，`getters` や `actions` 内の `this` の型を正しく推論できます．\n例えば `actions` 内では `this.count` で state にアクセスでき，`this.doubleCount` で getters にもアクセスできます．\n\n</KawaikoNote>\n\n### useStore 関数の実装\n\n```ts\nfunction useStore(outerStore?: Store | null): StoreGeneric {\n  const hasContext = hasInjectionContext();\n  // injection context から store を取得し，なければ activeStore にフォールバック\n  outerStore =\n    outerStore || (hasContext ? inject(storeSymbol, null) : null);\n\n  if (outerStore) setActiveStore(outerStore);\n\n  if (__DEV__ && !activeStore) {\n    throw new Error(\n      `[chibivue-store]: \"getActiveStore()\" was called but there was no active Store. ` +\n        `Are you trying to use a store before calling \"app.use(createStore())\"?`,\n    );\n  }\n\n  const store = activeStore!;\n\n  if (!store._s.has(id)) {\n    if (isSetupStore) {\n      createSetupStore(id, setup!, store);\n    } else if (options) {\n      createOptionsStore(id, options, store);\n    }\n  }\n\n  return store._s.get(id)!;\n}\n```\n\n処理の流れ：\n1. `hasInjectionContext()` で injection context があるか確認\n2. context があれば `inject(storeSymbol)` でルートストアを取得，なければ `activeStore` にフォールバック\n3. ストアが未作成なら `createSetupStore` または `createOptionsStore` で作成\n4. 作成済みのストアを返却\n\n<KawaikoNote variant=\"warning\" title=\"hasInjectionContext の重要性\">\n\n`hasInjectionContext()` は SSR で重要な役割を果たします．\nsetup 関数内では injection context があるため，`inject()` でリクエストごとのストアを取得できます．\ncontext がない場合（他のストア内など）は `activeStore` にフォールバックします．\n\n</KawaikoNote>\n\n### createSetupStore（Composition API 用）\n\n```ts\nfunction createSetupStore<Id extends string>(id: Id, setup: () => StateTree, store: Store) {\n  // ルート state にこのストアの状態を初期化\n  if (!store.state.value[id]) {\n    store.state.value[id] = {};\n  }\n\n  const initialState = store.state.value[id];\n  const setupStore = store._e.run(() => setup())!;\n\n  // setup store の戻り値を処理\n  for (const key in setupStore) {\n    const prop = setupStore[key];\n\n    // ref または reactive な値をルート state に同期\n    if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {\n      // SSR hydration: 初期状態があれば復元\n      if (initialState && key in initialState) {\n        if (isRef(prop)) {\n          prop.value = initialState[key];\n        } else {\n          Object.assign(prop, initialState[key]);\n        }\n      }\n      // ルート state に同期\n      store.state.value[id][key] = prop;\n    }\n  }\n\n  const _store = reactive({\n    $id: id,\n    ...setupStore,\n    $patch(partialState: Partial<StateTree> | ((state: StateTree) => void)) {\n      if (typeof partialState === \"function\") {\n        partialState(store.state.value[id]);\n      } else {\n        for (const key in partialState) {\n          const value = store.state.value[id][key];\n          if (isRef(value)) {\n            value.value = partialState[key];\n          } else {\n            store.state.value[id][key] = partialState[key];\n          }\n        }\n      }\n    },\n    $reset() {\n      console.warn(`[$reset] is not available in setup stores.`);\n    },\n  });\n\n  store._s.set(id, _store);\n}\n```\n\nポイント：\n- `store._e.run()` で EffectScope 内で setup を実行\n- ref/reactive な値を `store.state.value[id]` に同期（SSR 用）\n- hydration 時は `initialState` から値を復元\n\n<KawaikoNote variant=\"warning\" title=\"$reset の制限\">\n\nComposition API スタイルでは，初期状態を保持していないため `$reset` は使用できません．\n`$reset` が必要な場合は Options API スタイルを使用してください．\n\n</KawaikoNote>\n\n### createOptionsStore（Options API 用）\n\n```ts\nfunction createOptionsStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(id: Id, options: Omit<StoreOptions<Id, S, G, A>, \"id\">, store: Store) {\n  const { state: stateFn, getters, actions } = options;\n\n  // ルート state から初期状態を取得（SSR hydration 用）\n  const initialState = store.state.value[id] as S | undefined;\n\n  function setup() {\n    // 初期状態がなければ state() を実行\n    if (!initialState) {\n      store.state.value[id] = stateFn ? stateFn() : {};\n    }\n\n    const localState = toRefs(store.state.value[id]);\n\n    // getters を computed に変換\n    const computedGetters: Record<string, ComputedRef<unknown>> = {};\n    if (getters) {\n      for (const key in getters) {\n        const getter = getters[key];\n        computedGetters[key] = markRaw(\n          computed(() => {\n            setActiveStore(store);\n            const _store = store._s.get(id)!;\n            return getter.call(_store, _store);\n          }),\n        );\n      }\n    }\n\n    return {\n      ...localState,\n      ...computedGetters,\n      ...actions,\n    };\n  }\n\n  const setupStore = store._e.run(() => setup())!;\n\n  const _store = reactive({\n    $id: id,\n    ...setupStore,\n    ...boundActions,\n    $patch(partialState: Partial<S> | ((state: S) => void)) {\n      if (typeof partialState === \"function\") {\n        partialState(store.state.value[id] as S);\n      } else {\n        mergeReactiveObjects(store.state.value[id], partialState);\n      }\n    },\n    $reset() {\n      const newState = stateFn ? stateFn() : ({} as S);\n      this.$patch(($state: S) => {\n        Object.assign($state, newState);\n      });\n    },\n  });\n\n  // $state を getter/setter として定義\n  Object.defineProperty(_store, \"$state\", {\n    get: () => store.state.value[id],\n    set: (state) => {\n      _store.$patch(($state: S) => {\n        Object.assign($state, state);\n      });\n    },\n  });\n\n  store._s.set(id, _store);\n}\n```\n\nポイント：\n- 状態は `store.state.value[id]` に保存（Pinia と同じパターン）\n- `toRefs` で状態を ref に変換し，リアクティビティを維持\n- `store._e.run()` で EffectScope 内で setup を実行\n- `getters` は `computed` に変換し，`markRaw` でリアクティブ化を回避\n- `$state` は getter/setter として定義し，状態全体にアクセス可能\n\n## 使用例\n\n### Composition API スタイル\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", () => {\n  // State\n  const count = ref(0);\n\n  // Getters (computed を使用)\n  const doubleCount = computed(() => count.value * 2);\n\n  // Actions\n  const increment = () => {\n    count.value++;\n  };\n\n  const reset = () => {\n    count.value = 0;\n  };\n\n  return {\n    count,\n    doubleCount,\n    increment,\n    reset,\n  };\n});\n```\n\n### Options API スタイル\n\n```ts\n// stores/counter.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", {\n  state: () => ({\n    count: 0,\n  }),\n\n  getters: {\n    doubleCount(state) {\n      return state.count * 2;\n    },\n    // this を使って他の getter にアクセス\n    quadrupleCount() {\n      return this.doubleCount * 2;\n    },\n  },\n\n  actions: {\n    increment() {\n      this.count++;\n    },\n    // 非同期アクションも可能\n    async incrementAsync() {\n      await new Promise((resolve) => setTimeout(resolve, 1000));\n      this.count++;\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"funny\" title=\"どちらのスタイルを選ぶ？\">\n\n- **Composition API スタイル**: 柔軟性が高く，通常のコンポーネントと同じ書き方ができる\n- **Options API スタイル**: 構造が明確で，`$reset` が使える\n\nどちらも同等の機能を提供しますが，プロジェクトの方針に合わせて選んでください．\n\n</KawaikoNote>\n\n### アプリケーションへの登録\n\n```ts\n// main.ts\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\nimport { createStore } from \"chibivue-store\";\n\nconst app = createApp(App);\napp.use(createStore());\napp.mount(\"#app\");\n```\n\n### コンポーネントでの使用\n\n```vue\n<!-- Counter.vue -->\n<script setup>\nimport { useCounterStore } from \"../stores/counter\";\n\nconst counterStore = useCounterStore();\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ counterStore.count }}</p>\n    <p>Double: {{ counterStore.doubleCount }}</p>\n    <button @click=\"counterStore.increment\">Increment</button>\n  </div>\n</template>\n```\n\n## $patch の使い方\n\n`$patch` を使うと，複数の状態を一度に更新できます．\n\n### オブジェクト形式\n\n```ts\nconst store = useCounterStore();\n\n// 複数のプロパティを一度に更新\nstore.$patch({\n  count: 10,\n});\n```\n\n### 関数形式\n\n```ts\nconst store = useCounterStore();\n\n// state を直接操作\nstore.$patch((state) => {\n  state.count += 5;\n});\n```\n\n<KawaikoNote variant=\"warning\" title=\"$patch の利点\">\n\n複数の状態変更を `$patch` でまとめると，リアクティビティのトリガーが一度だけになり，パフォーマンスが向上します．\n\n</KawaikoNote>\n\n## $reset の使い方\n\nOptions API スタイルで定義したストアでは，`$reset` を使って状態を初期値に戻せます．\n\n```ts\nconst store = useCounterStore();\n\nstore.increment(); // count: 1\nstore.increment(); // count: 2\n\nstore.$reset(); // count: 0 (初期値に戻る)\n```\n\n## 処理フロー\n\n```\napp.use(createStore())\n  ↓\nstore.install(app)\n  ├── setActiveStore(store)\n  └── app.provide(storeSymbol, store)\n  ↓\nコンポーネントで useCounterStore() を呼び出し\n  ↓\nuseStore()\n  ├── inject(storeSymbol) で store を取得\n  └── store._s.has(\"counter\") をチェック\n      ↓ (なければ)\n      createSetupStore() または createOptionsStore()\n        ├── setup() / state() を実行\n        ├── getters を computed に変換\n        ├── actions をバインド\n        └── store._s.set(\"counter\", 結果)\n  ↓\nstore._s.get(\"counter\") を返却\n  ↓\nコンポーネントでリアクティブな状態を使用\n```\n\n## 複数のストア\n\n複数のストアを定義して使い分けることができます．\n\n```ts\n// stores/user.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useUserStore = defineStore(\"user\", {\n  state: () => ({\n    name: \"\",\n    isLoggedIn: false,\n  }),\n\n  actions: {\n    login(userName: string) {\n      this.name = userName;\n      this.isLoggedIn = true;\n    },\n    logout() {\n      this.$reset();\n    },\n  },\n});\n```\n\n```ts\n// stores/cart.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCartStore = defineStore(\"cart\", {\n  state: () => ({\n    items: [] as { id: number; name: string; price: number }[],\n  }),\n\n  getters: {\n    total(state) {\n      return state.items.reduce((sum, item) => sum + item.price, 0);\n    },\n    itemCount(state) {\n      return state.items.length;\n    },\n  },\n\n  actions: {\n    addItem(item: { id: number; name: string; price: number }) {\n      this.items.push(item);\n    },\n    clearCart() {\n      this.$reset();\n    },\n  },\n});\n```\n\n### ストア間の連携\n\nあるストアから別のストアを使用することもできます．\n\n```ts\n// stores/checkout.ts\nimport { defineStore } from \"chibivue-store\";\nimport { useUserStore } from \"./user\";\nimport { useCartStore } from \"./cart\";\n\nexport const useCheckoutStore = defineStore(\"checkout\", {\n  actions: {\n    checkout() {\n      const userStore = useUserStore();\n      const cartStore = useCartStore();\n\n      if (!userStore.isLoggedIn) {\n        throw new Error(\"Please login first\");\n      }\n\n      console.log(`${userStore.name} purchased ${cartStore.itemCount} items`);\n      console.log(`Total: ${cartStore.total}`);\n\n      cartStore.clearCart();\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"warning\" title=\"循環参照に注意\">\n\nストア A がストア B を使用し，ストア B がストア A を使用すると循環参照が発生します．\nこのような場合は，共通の状態を別のストアに切り出すことを検討してください．\n\n</KawaikoNote>\n\n## SSR 対応\n\nchibivue-store はサーバーサイドレンダリング（SSR）に対応しています．\n\n### store.state プロパティ\n\nルートストアの `state` プロパティを使って，すべてのストアの状態をシリアライズ・ハイドレートできます．\n\n```ts\n// Store interface\ninterface Store {\n  install: (app: App) => void;\n  state: Ref<Record<string, StateTree>>;  // すべてのストアの状態を保持\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n`state` は `ref({})` として作成され，各ストアの状態が `state.value[storeId]` に保存されます．\nこれにより：\n- SSR でサーバー側の状態をシリアライズ: `JSON.stringify(store.state.value)`\n- クライアント側でハイドレート: `store.state.value = serverState`\n\n### サーバー側：状態のシリアライズ\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // 重要: リクエストごとに新しいインスタンスを作成\n  // これにより Cross-Request State Pollution を防ぐ\n  const store = createStore();\n  const app = createApp(App);\n  app.use(store);\n\n  const html = await renderToString(app);\n\n  // ストアの状態をシリアライズ\n  const storeState = JSON.stringify(store.state.value);\n\n  return { html, storeState };\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"リクエストごとに新しいインスタンス\">\n\n`render()` 関数内で `createStore()` と `createApp()` を呼び出していることに注目してください．\n**モジュールスコープでシングルトンとして作成してはいけません**．\n\n```ts\n// NG: モジュールスコープでの作成は危険\nconst store = createStore();  // 全リクエストで共有されてしまう\nconst app = createApp(App);\n\nexport async function render() {\n  // store と app が全リクエストで共有される\n}\n```\n\n</KawaikoNote>\n\n### HTML への埋め込み\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      // サーバーでシリアライズした状態を埋め込む\n      window.__STORE_STATE__ = ${storeState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### クライアント側：状態のハイドレート\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nconst store = createStore();\nconst app = createApp(App);\napp.use(store);\n\n// サーバーの状態でハイドレート\nif (window.__STORE_STATE__) {\n  store.state.value = window.__STORE_STATE__;\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"warning\" title=\"ストアの初期化順序\">\n\nハイドレートを行う前に，各ストアが初期化されている必要があります．\nコンポーネントが使用するストア（`useXxxStore()`）は，`app.mount()` 時に自動的に初期化されます．\n\nもし mount 前にハイドレートが必要な場合は，事前にストアを呼び出しておいてください：\n\n```ts\n// 事前にストアを初期化\nuseCounterStore();\nuseUserStore();\n\n// その後ハイドレート\nstore.state = window.__STORE_STATE__;\n\napp.mount(\"#app\");\n```\n\n</KawaikoNote>\n\n### state の仕組み\n\n新しい実装では，`state` は `ref({})` として作成され，各ストアの状態が直接保存されます：\n\n```ts\n// createStore.ts\nconst state = scope.run(() => ref({}))!;\n```\n\n各ストアが作成されると，状態は `store.state.value[id]` に保存されます：\n\n```ts\n// store.ts (createSetupStore, createOptionsStore 内)\nstore.state.value[id] = stateFn ? stateFn() : {};\n```\n\nこの設計により：\n- SSR: `store.state.value` をそのまま `JSON.stringify` でシリアライズ\n- Hydration: `store.state.value = serverState` で直接復元\n- 各ストアの setup/state 関数は，既存の `state.value[id]` があればそれを使用（hydration）\n\n<KawaikoNote variant=\"surprise\" title=\"SSR Ready!\">\n\nこれで chibivue-store は SSR に対応しました．\nサーバーで計算した状態をクライアントに引き継ぐことで，ハイドレーション後も一貫した状態を維持できます．\n\n</KawaikoNote>\n\n## 今後の拡張\n\n現在の実装は基本的な機能をカバーしていますが，Pinia には以下のような機能もあります：\n\n1. **$subscribe**: 状態変更の購読\n2. **$onAction**: アクション実行の監視\n3. **プラグインシステム**: ストアの機能を拡張\n4. **Devtools 連携**: 状態の可視化とタイムトラベルデバッグ\n5. **mapState / mapActions**: Options API コンポーネント用のヘルパー\n\n<KawaikoNote variant=\"surprise\" title=\"実装完了！\">\n\nこれで Pinia ライクなストアが完成しました．\n約 150 行のコードで状態管理を実現できています．\nPinia の仕組みを理解する良い出発点になったでしょう．\n\n</KawaikoNote>\n\n## まとめ\n\nchibivue-store の実装は以下の要素で構成されています：\n\n1. **ルートストアの作成**: `createStore` で Vue プラグインとしてインストール\n2. **Dependency Injection**: `provide/inject` でストアをコンポーネントツリー全体に共有\n3. **2 つの定義スタイル**: Composition API と Options API の両方をサポート\n4. **Getters**: `computed` を使った派生状態の定義\n5. **Actions**: state と getters にアクセスできるメソッド\n6. **$patch**: 状態の一括更新\n7. **$reset**: 状態の初期値へのリセット（Options API のみ）\n8. **シングルトンパターン**: 同じ ID のストアは一度だけ作成\n9. **SSR 対応**: `store.state` による状態のシリアライズとハイドレート\n\nVue のプラグインシステム，provide/inject，リアクティビティシステムを組み合わせることで，グローバルな状態管理を実現しています．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/010-plugins/030-data-fetch.md",
    "content": "# Data Fetch\n\n## データフェッチライブラリとは\n\nモダンな Web アプリケーションでは，サーバーからのデータ取得が頻繁に行われます．Vue.js エコシステムでは，Pinia Colada や TanStack Query などのライブラリがこの機能を提供しています．\n\nこの章では，Pinia Colada のような基本的なデータフェッチ機能を chibivue-fetch として実装します．\n\n### なぜライブラリが必要なのか\n\n単純なデータ取得は `fetch` と `ref` で十分に見えます：\n\n```ts\n// composables/useUser.ts\nimport { ref, onMounted } from \"chibivue\";\n\nexport function useUser(id: number) {\n  const user = ref(null);\n  const isLoading = ref(true);\n  const error = ref(null);\n\n  onMounted(async () => {\n    try {\n      const response = await fetch(`/api/users/${id}`);\n      user.value = await response.json();\n    } catch (e) {\n      error.value = e;\n    } finally {\n      isLoading.value = false;\n    }\n  });\n\n  return { user, isLoading, error };\n}\n```\n\nしかし，この実装には以下の問題があります：\n\n1. **キャッシュがない**: 同じデータを何度もフェッチしてしまう\n2. **SSR 対応が難しい**: サーバーで取得したデータをクライアントに引き継げない\n3. **重複リクエスト**: 同じコンポーネントを複数マウントすると重複リクエストが発生\n4. **エラーハンドリング**: リトライや再フェッチのロジックが複雑になる\n\nデータフェッチライブラリは，これらの問題を解決し，宣言的な API を提供します．\n\n## パッケージ構成\n\nchibivue-fetch は `@extensions/chibivue-fetch` パッケージで提供されています．\n\n```\n@extensions/chibivue-fetch/src/\n├── index.ts           # エクスポート\n├── queryCache.ts      # QueryCache の実装（キャッシュ管理）\n├── useQuery.ts        # データ取得用フック\n├── useMutation.ts     # データ変更用フック\n└── types.ts           # 型定義\n```\n\n## Data State パターン\n\nPinia Colada と同様に，chibivue-fetch はデータの状態を 3 つの状態で表現します：\n\n```ts\ntype DataStateStatus = \"pending\" | \"error\" | \"success\";\n\ntype DataState<TData, TError> =\n  | { status: \"pending\"; data: undefined; error: null }\n  | { status: \"error\"; data: TData | undefined; error: TError }\n  | { status: \"success\"; data: TData; error: null };\n```\n\nこの状態モデルにより，データの状態を明確に追跡できます．\n\n## QueryCache\n\n`QueryCache` はキャッシュの管理と SSR のためのステート管理を担います．\n\n```ts\n// queryCache.ts\nexport interface QueryCache {\n  install: (app: App) => void;\n  caches: Map<string, UseQueryEntry>;\n  options: Required<QueryCacheOptions>;\n  create: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults | null, ...) => UseQueryEntry;\n  ensure: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults) => UseQueryEntry;\n  fetch: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  refresh: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  invalidate: (entry: UseQueryEntry) => void;\n  invalidateQueries: (key?: EntryKey) => void;\n  remove: (entry: UseQueryEntry) => void;\n  track: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  untrack: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  setQueryData: <TData>(key: EntryKey, data: TData) => void;\n  getQueryData: <TData>(key: EntryKey) => TData | undefined;\n  prefetchQuery: <TData>(key: EntryKey, queryFn: (ctx: QueryContext) => Promise<TData>, options?: Partial<UseQueryOptionsWithDefaults>) => Promise<void>;\n  isStale: (entry: UseQueryEntry) => boolean;\n}\n```\n\n### 主要なメソッド\n\n- `ensure`: エントリを取得または作成\n- `fetch`: クエリを実行（常に実行）\n- `refresh`: クエリをリフレッシュ（stale または error の場合のみ実行）\n- `invalidate`: エントリを無効化（stale にする）\n- `invalidateQueries`: キーに一致するエントリを無効化\n- `track` / `untrack`: コンポーネントの依存関係を追跡\n- `setQueryData` / `getQueryData`: キャッシュデータの直接操作\n- `prefetchQuery`: 事前にデータをフェッチしてキャッシュに格納\n\n### createQueryCache\n\n```ts\nimport { createQueryCache } from \"chibivue-fetch\";\n\nconst queryCache = createQueryCache({\n  staleTime: 5000,       // デフォルトの stale time (5秒)\n  gcTime: 300000,        // デフォルトの GC time (5分)\n});\n\napp.use(queryCache);\n```\n\n## useQuery\n\n`useQuery` はデータ取得のためのコンポーザブルです．\n\n```ts\n// useQuery.ts\nexport interface UseQueryOptions<TData = unknown, TError = Error> {\n  key: EntryKey | EntryKeyFn;\n  query: (context: QueryContext) => Promise<TData>;\n  staleTime?: number;\n  gcTime?: number;\n  refetchOnMount?: boolean | \"always\";\n  initialData?: TData | (() => TData);\n  enabled?: boolean | Ref<boolean> | ComputedRef<boolean>;\n  retry?: number | boolean;\n  retryDelay?: number;\n  meta?: QueryMeta;\n}\n\nexport interface UseQueryReturn<TData = unknown, TError = Error> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  refresh: () => Promise<DataState<TData, TError>>;\n  refetch: () => Promise<DataState<TData, TError>>;\n}\n```\n\n### オプション\n\n- `key`: クエリの一意なキー（キャッシュのキーになる）\n- `query`: データを取得する非同期関数（`{ signal }` を受け取る）\n- `staleTime`: データが「stale（古い）」になるまでの時間\n- `gcTime`: 未使用のキャッシュを保持する期間（ガベージコレクション）\n- `enabled`: クエリを有効にするかどうか\n- `retry`: エラー時のリトライ回数\n- `initialData`: 初期データ\n\n### 状態\n\n- `status`: 現在の状態（`\"pending\"` | `\"error\"` | `\"success\"`）\n- `asyncStatus`: 非同期状態（`\"idle\"` | `\"loading\"`）\n- `isPending`: 初期データがまだない状態\n- `isLoading`: 初回フェッチ中（`isPending` かつ `asyncStatus === \"loading\"`）\n- `isSuccess`: フェッチ成功\n- `isError`: フェッチ失敗\n\n### refresh と refetch の違い\n\n- `refresh()`: stale または error の場合のみフェッチ\n- `refetch()`: 常にフェッチ（キャッシュを無効化してから）\n\n### 使用例\n\n```ts\nimport { useQuery } from \"chibivue-fetch\";\n\nconst { data, isLoading, error, refresh } = useQuery({\n  key: [\"user\", userId],\n  query: ({ signal }) => fetch(`/api/users/${userId}`, { signal }).then((res) => res.json()),\n  staleTime: 60000, // 1分間はキャッシュを使用\n});\n```\n\n## useMutation\n\n`useMutation` はデータ変更（POST, PUT, DELETE など）のためのコンポーザブルです．\n\n```ts\n// useMutation.ts\nexport interface UseMutationOptions<TData, TError, TVariables, TContext> {\n  mutation: (variables: TVariables) => Promise<TData>;\n  onMutate?: (variables: TVariables) => TContext | Promise<TContext>;\n  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n}\n\nexport interface UseMutationReturn<TData, TError, TVariables> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  variables: ShallowRef<TVariables | undefined>;\n  mutate: (variables: TVariables) => void;\n  mutateAsync: (variables: TVariables) => Promise<TData>;\n  reset: () => void;\n}\n```\n\n### ライフサイクルコールバック\n\n- `onMutate`: mutation 実行前に呼ばれる（context を返す）\n- `onSuccess`: 成功時に呼ばれる\n- `onError`: エラー時に呼ばれる\n- `onSettled`: 成功・エラーに関わらず最後に呼ばれる\n\n### 使用例\n\n```ts\nimport { useMutation } from \"chibivue-fetch\";\n\nconst { mutate, isLoading, isSuccess } = useMutation({\n  mutation: (newUser) => fetch(\"/api/users\", {\n    method: \"POST\",\n    body: JSON.stringify(newUser),\n  }).then((res) => res.json()),\n  onSuccess: (data) => {\n    console.log(\"User created:\", data);\n    // キャッシュを無効化して再フェッチをトリガー\n    queryCache.invalidateQueries([\"users\"]);\n  },\n});\n\n// 使用\nmutate({ name: \"John\", email: \"john@example.com\" });\n```\n\n## キャッシュの仕組み\n\n### Entry Key\n\n`key` はキャッシュのキーとして使用されます．配列形式で階層的なキーを表現できます：\n\n```ts\n// 単純なキー\nkey: [\"users\"]\n\n// 階層的なキー\nkey: [\"users\", userId]\n\n// オブジェクトを含むキー\nkey: [\"users\", { status: \"active\", page: 1 }]\n```\n\n同じ `key` を持つクエリはキャッシュを共有します．キーはソートされた JSON として直列化されるため，オブジェクトのプロパティの順序は関係ありません．\n\n### Stale Time と GC Time\n\n```\n       ← staleTime →|← refetch window →|← gcTime →|\n  fetch             stale               inactive   gc\n    |-----------------|----------------------|-----|\n    data arrives      data is stale         data removed\n```\n\n- **staleTime**: データが「fresh」である期間．この間は `refresh()` を呼んでもフェッチしない\n- **gcTime**: 未使用のキャッシュを保持する期間．コンポーネントがアンマウントされてから，この期間が経過するとキャッシュが削除される\n\n```ts\n// 1分間は再フェッチしない，5分間キャッシュを保持\nuseQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  staleTime: 60 * 1000,  // 1 minute\n  gcTime: 5 * 60 * 1000, // 5 minutes\n});\n```\n\n### 依存関係追跡\n\nPinia Colada と同様に，chibivue-fetch は各クエリエントリがどのコンポーネントで使用されているかを追跡します：\n\n```ts\n// コンポーネントがマウントされると track\nonMounted(() => {\n  queryCache.track(entry, currentInstance);\n});\n\n// コンポーネントがアンマウントされると untrack\nonUnmounted(() => {\n  queryCache.untrack(entry, currentInstance);\n});\n```\n\n依存関係がなくなると，`gcTime` 後にキャッシュがガベージコレクションされます．\n\n## SSR 対応\n\nchibivue-fetch は SSR に対応しています．\n\n### サーバー側：状態のシリアライズ\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createQueryCache, serializeQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // リクエストごとに新しいインスタンスを作成\n  const queryCache = createQueryCache();\n  const app = createApp(App);\n  app.use(queryCache);\n\n  // prefetch でサーバー側でデータを取得\n  await queryCache.prefetchQuery(\n    [\"users\"],\n    ({ signal }) => fetch(\"http://api/users\", { signal }).then((r) => r.json()),\n  );\n\n  const html = await renderToString(app);\n\n  // キャッシュ状態をシリアライズ\n  const queryState = JSON.stringify(serializeQueryCache(queryCache));\n\n  return { html, queryState };\n}\n```\n\n### シリアライズ形式\n\nPinia Colada と同様に，相対タイムスタンプを使用してシリアライズします：\n\n```ts\n// UseQueryEntryNodeSerialized = [data, error, when (relative), meta]\n{\n  '[\"users\"]': [\n    [{ id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }], // data\n    null,                                                // error\n    0,                                                   // when (relative: now - fetchTime)\n    undefined                                            // meta\n  ]\n}\n```\n\n相対タイムスタンプにより，サーバーとクライアントの時刻のずれを考慮できます．\n\n### HTML への埋め込み\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.__QUERY_STATE__ = ${queryState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### クライアント側：状態のハイドレート\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createQueryCache, hydrateQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nconst queryCache = createQueryCache();\nconst app = createApp(App);\napp.use(queryCache);\n\n// サーバーの状態でハイドレート\nif (window.__QUERY_STATE__) {\n  hydrateQueryCache(queryCache, window.__QUERY_STATE__);\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\nSSR では，Store と同様に **Cross-Request State Pollution** に注意が必要です．\n`createQueryCache()` は `render()` 関数内で呼び出し，リクエストごとに新しいインスタンスを作成してください．\n\n</KawaikoNote>\n\n## 実践的な使用例\n\n### リアクティブな Query Key\n\n```ts\nimport { ref, computed } from \"chibivue\";\nimport { useQuery } from \"chibivue-fetch\";\n\nconst page = ref(1);\nconst filters = ref({ status: \"active\" });\n\nconst { data, isLoading } = useQuery({\n  // 関数形式で動的なキーを生成\n  key: () => [\"users\", { page: page.value, ...filters.value }],\n  query: ({ signal }) => fetchUsers(page.value, filters.value, signal),\n});\n\n// page や filters が変わると自動的に再フェッチ\nfunction nextPage() {\n  page.value++;\n}\n```\n\n### 条件付きクエリ\n\n```ts\nconst userId = ref<number | null>(null);\n\nconst { data: user } = useQuery({\n  key: () => [\"user\", userId.value],\n  query: ({ signal }) => fetchUser(userId.value!, signal),\n  // userId が null の間はクエリを実行しない\n  enabled: computed(() => userId.value !== null),\n});\n```\n\n### Mutation 後のキャッシュ更新\n\n```ts\nconst queryCache = getActiveQueryCache();\n\nconst { mutate: createUser } = useMutation({\n  mutation: (newUser) => api.createUser(newUser),\n  onSuccess: (createdUser) => {\n    // 方法1: キャッシュを無効化して再フェッチ\n    queryCache.invalidateQueries([\"users\"]);\n\n    // 方法2: キャッシュを直接更新（楽観的更新）\n    const currentUsers = queryCache.getQueryData<User[]>([\"users\"]);\n    if (currentUsers) {\n      queryCache.setQueryData([\"users\"], [...currentUsers, createdUser]);\n    }\n  },\n});\n```\n\n### エラーハンドリングとリトライ\n\n```ts\nconst { data, error, refresh } = useQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  retry: 3,        // 3回までリトライ\n  retryDelay: 1000, // 1秒後にリトライ\n});\n\n// コンポーネント内で\nif (error.value) {\n  // エラー表示とリトライボタン\n}\n```\n\n### AbortController による中断\n\n```ts\nconst { data } = useQuery({\n  key: [\"users\"],\n  query: async ({ signal }) => {\n    const response = await fetch(\"/api/users\", { signal });\n    if (!response.ok) throw new Error(\"Failed to fetch\");\n    return response.json();\n  },\n});\n```\n\nクエリが中断されると（新しいリクエストが開始された場合など），`signal` が abort されます．\n\n## まとめ\n\nchibivue-fetch の実装は以下の要素で構成されています：\n\n1. **QueryCache**: キャッシュの一元管理と依存関係追跡\n2. **Data State パターン**: `pending | error | success` の 3 状態モデル\n3. **useQuery**: 宣言的なデータ取得 API\n4. **useMutation**: データ変更の管理とライフサイクルコールバック\n5. **キャッシュ戦略**: staleTime / gcTime による柔軟な制御\n6. **SSR 対応**: `serializeQueryCache()` / `hydrateQueryCache()` によるステートの転送\n7. **リアクティブキー**: 動的なクエリキーのサポート\n8. **エラーハンドリング**: 自動リトライと状態管理\n9. **AbortController**: リクエストの中断サポート\n\nPinia Colada の主要な機能をミニマルに実装することで，データフェッチの仕組みを理解できます．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/010-plugins/040-language-tools.md",
    "content": "# Language Tools\n\n## Language Tools とは？\n\nLanguage Tools は `.vue` Single File Components (SFCs) に対する IDE サポートを提供します．以下のような機能を実現します：\n\n- シンタックスハイライト\n- 自動補完\n- 型チェック\n- 定義へ移動\n- エラー診断\n\nVue.js エコシステムでは，[vuejs/language-tools](https://github.com/vuejs/language-tools) がこの機能を提供しており，[Volar.js](https://volarjs.dev/) を基盤として構築されています．この章では，Volar.js を使って chibivue 用の最小限の Language Tools を実装します．\n\n## なぜ Language Tools が必要なのか？\n\nTypeScript の言語サービスは `.ts` や `.tsx` ファイルしか理解できません．しかし `.vue` ファイルは以下のように複数の言語が混在しています：\n\n```vue\n<template>\n  <div>{{ message }}</div>  <!-- HTML + 式 -->\n</template>\n\n<script setup lang=\"ts\">\nconst message = ref('Hello')  // TypeScript\n</script>\n\n<style scoped>\ndiv { color: red; }  /* CSS */\n</style>\n```\n\nLanguage Tools の役割は，この複合的なファイルを TypeScript 言語サービスが理解できる形式に**変換**することです．この変換により，`.vue` ファイル内でも TypeScript の全機能（型チェック，自動補完，リファクタリングなど）が利用可能になります．\n\n## アーキテクチャ概要\n\nLanguage Tools は 3 つの主要パッケージで構成されます：\n\n```txt\n@extensions/\n├── chibivue-language-core/     # コア言語処理\n│   ├── parseSfc.ts             # SFC パーサー\n│   ├── virtualCode.ts          # 仮想コード生成\n│   ├── languagePlugin.ts       # Volar.js プラグイン\n│   └── types.ts                # 型定義\n├── chibivue-language-server/   # LSP サーバー\n│   └── server.ts               # Language Server Protocol サーバー\n└── vscode-chibivue/            # VSCode 拡張機能\n    ├── extension.ts            # 拡張機能エントリーポイント\n    ├── syntaxes/               # TextMate 文法\n    └── language-configuration.json\n```\n\n### データフロー\n\nエディタで `.vue` ファイルを編集したとき，以下の流れでデータが処理されます：\n\n```txt\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                              VSCode                                         │\n│  ┌─────────────┐                                                            │\n│  │  App.vue    │  ユーザーが .vue ファイルを編集                            │\n│  └──────┬──────┘                                                            │\n│         │                                                                   │\n│         ▼                                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  vscode-chibivue    │  VSCode 拡張機能がファイル変更を検知               │\n│  │  (Language Client)  │                                                    │\n│  └──────────┬──────────┘                                                    │\n└─────────────┼───────────────────────────────────────────────────────────────┘\n              │ LSP (Language Server Protocol)\n              ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│  chibivue-language-server                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  Language Server    │  LSP リクエストを受信                              │\n│  └──────────┬──────────┘                                                    │\n│             │                                                               │\n│             ▼                                                               │\n│  ┌─────────────────────┐    ┌─────────────────────┐                         │\n│  │  chibivue-language  │───▶│  Virtual Code       │                         │\n│  │  -core (Plugin)     │    │  (.vue → .ts 変換)  │                         │\n│  └─────────────────────┘    └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  TypeScript         │                         │\n│                             │  Language Service   │  型チェック・補完など   │\n│                             └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  Code Mappings      │  結果を元の位置に変換   │\n│                             └─────────────────────┘                         │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n## コアコンセプト\n\n### 仮想コード (Virtual Code)\n\nLanguage Tools の中核となる概念は**仮想コード**です．`.vue` ファイルを TypeScript に変換することで，TypeScript 言語サービスの全機能を活用できます．\n\n#### 変換の例\n\n```vue\n<!-- 元の .vue ファイル -->\n<template>\n  <div>{{ message }}</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n</script>\n```\n\nこれは以下の仮想 TypeScript に変換されます：\n\n```ts\n// 仮想 TypeScript コード\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n\n// テンプレート内の式を型チェックするためのコード\n// 実際には実行されないが，TypeScript が式の型を検証できる\ndeclare const __VLS_template: () => void;\n(() => {\n  // テンプレート内の {{ message }} に対応\n  // message が存在し，型が正しいことを TypeScript が検証\n  const __VLS_expr0 = (message);\n})();\n```\n\nこの変換により：\n- `message` の型が `Ref<string>` であることが検証される\n- `message` が未定義の場合，エラーが報告される\n- `message` にホバーすると型情報が表示される\n\n### コードマッピング\n\n仮想コード内の位置を元の `.vue` ファイルの位置に対応付けるのが**コードマッピング**です．\n\n```txt\n元の .vue ファイル                    仮想 TypeScript\n─────────────────────────────────────────────────────────────\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'  ←──→  import { ref } from 'chibivue'\n                                      ↑\nconst message = ref('Hello')    ←──→  const message = ref('Hello')\n</script>                             ↑\n                                      │\n<template>                            │\n  <div>{{ message }}</div>      ←─────┼──→  const __VLS_expr0 = (message);\n</template>                           │\n                                      ↓\n                                マッピングにより位置が対応付けられる\n```\n\nマッピングがあることで：\n- 仮想コードでエラーが発生 → 元の `.vue` ファイルの正しい位置にエラーを表示\n- 「定義へ移動」を実行 → 仮想コードの定義位置を元のファイルの位置に変換\n\n## 実装\n\n### 型定義\n\nまず，SFC の構造を表現する型を定義します．\n\n```ts\n// types.ts\n\n/**\n * SFC 内の各ブロック（template, script, style）を表現する型\n */\nexport interface SfcBlock {\n  /** ブロックの種類（\"template\", \"script\", \"style\" など） */\n  type: string;\n\n  /** ブロック内のコンテンツ（タグを除いた中身） */\n  content: string;\n\n  /** ブロックの位置情報（エラー表示やマッピングに使用） */\n  loc: {\n    start: { line: number; column: number; offset: number };\n    end: { line: number; column: number; offset: number };\n  };\n\n  /** ブロックの属性（例：lang=\"ts\", scoped など） */\n  attrs: Record<string, string | true>;\n\n  /** 言語指定（attrs.lang のショートカット） */\n  lang?: string;\n}\n\n/**\n * パース結果の SFC 全体を表現する型\n */\nexport interface SfcDescriptor {\n  /** <template> ブロック */\n  template: SfcBlock | null;\n\n  /** <script>（setup なし）ブロック */\n  script: SfcBlock | null;\n\n  /** <script setup> ブロック */\n  scriptSetup: SfcBlock | null;\n\n  /** <style> ブロック（複数可） */\n  styles: SfcBlock[];\n\n  /** カスタムブロック（<docs> など） */\n  customBlocks: SfcBlock[];\n}\n```\n\n### SFC パーサー\n\n`.vue` ファイルをパースして `SfcDescriptor` を生成します．\n\n::: tip\n実際の実装では，chibivue に実装済みの `@chibivue/compiler-sfc` パッケージの `parse` 関数を使用できます．ここでは教育目的で簡略化したパーサーを示します．\n:::\n\n```ts\n// parseSfc.ts\nimport type { SfcBlock, SfcDescriptor } from './types';\n\n/**\n * .vue ファイルの内容をパースして SfcDescriptor を返す\n *\n * @param content - .vue ファイルの内容\n * @param fileName - ファイル名（エラーメッセージ用）\n */\nexport function parseSfc(content: string, fileName: string): SfcDescriptor {\n  const descriptor: SfcDescriptor = {\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n    customBlocks: [],\n  };\n\n  // トップレベルのブロックにマッチする正規表現\n  // <tagName attrs>content</tagName> の形式を検出\n  const blockRegex = /<(\\w+)([^>]*)>([\\s\\S]*?)<\\/\\1>/g;\n  let match: RegExpExecArray | null;\n\n  while ((match = blockRegex.exec(content)) !== null) {\n    const [fullMatch, tagName, attrsString, blockContent] = match;\n\n    // ブロックの開始位置を計算\n    const startOffset = match.index + `<${tagName}${attrsString}>`.length;\n    const startPos = offsetToPosition(content, startOffset);\n\n    // ブロックの終了位置を計算\n    const endOffset = startOffset + blockContent.length;\n    const endPos = offsetToPosition(content, endOffset);\n\n    // 属性をパース（例：\"lang=\"ts\" scoped\" → { lang: \"ts\", scoped: true }）\n    const attrs = parseAttrs(attrsString);\n\n    const block: SfcBlock = {\n      type: tagName,\n      content: blockContent,\n      loc: {\n        start: { ...startPos, offset: startOffset },\n        end: { ...endPos, offset: endOffset },\n      },\n      attrs,\n      lang: typeof attrs.lang === 'string' ? attrs.lang : undefined,\n    };\n\n    // ブロックの種類に応じて振り分け\n    switch (tagName) {\n      case 'template':\n        descriptor.template = block;\n        break;\n      case 'script':\n        // setup 属性の有無で振り分け\n        if ('setup' in attrs) {\n          descriptor.scriptSetup = block;\n        } else {\n          descriptor.script = block;\n        }\n        break;\n      case 'style':\n        descriptor.styles.push(block);\n        break;\n      default:\n        descriptor.customBlocks.push(block);\n    }\n  }\n\n  return descriptor;\n}\n\n/**\n * オフセット（文字位置）から行・列番号を計算\n */\nfunction offsetToPosition(\n  content: string,\n  offset: number\n): { line: number; column: number } {\n  const lines = content.slice(0, offset).split('\\n');\n  return {\n    line: lines.length,\n    column: lines[lines.length - 1].length + 1,\n  };\n}\n\n/**\n * 属性文字列をパースしてオブジェクトに変換\n * 例: ' lang=\"ts\" scoped' → { lang: \"ts\", scoped: true }\n */\nfunction parseAttrs(attrsString: string): Record<string, string | true> {\n  const attrs: Record<string, string | true> = {};\n  const attrRegex = /(\\w+)(?:=\"([^\"]*)\"|='([^']*)')?/g;\n  let attrMatch: RegExpExecArray | null;\n\n  while ((attrMatch = attrRegex.exec(attrsString)) !== null) {\n    const [, name, doubleQuoted, singleQuoted] = attrMatch;\n    attrs[name] = doubleQuoted ?? singleQuoted ?? true;\n  }\n\n  return attrs;\n}\n```\n\n### 仮想コード生成\n\nVolar.js の `VirtualCode` インターフェースを実装します．これが Language Tools の心臓部です．\n\n```ts\n// virtualCode.ts\nimport type {\n  CodeMapping,\n  VirtualCode,\n} from '@volar/language-core';\nimport type * as ts from 'typescript';\nimport { parseSfc } from './parseSfc';\nimport type { SfcDescriptor } from './types';\n\n/**\n * コードセグメント：生成コードの一部とそのマッピング情報\n */\ntype CodeSegment = [\n  code: string,                           // 生成するコード\n  sourceOffsetStart?: number,             // 元ファイルでの開始位置\n  sourceOffsetEnd?: number,               // 元ファイルでの終了位置\n  features?: { verification?: boolean },  // マッピングの機能設定\n];\n\n/**\n * .vue ファイルを仮想 TypeScript コードに変換するクラス\n */\nexport class ChibivueVirtualCode implements VirtualCode {\n  id = 'root';\n  languageId = 'vue';\n  snapshot: ts.IScriptSnapshot;\n  mappings: CodeMapping[] = [];\n  embeddedCodes: VirtualCode[] = [];\n\n  private fileName: string;\n  private sfc: SfcDescriptor;\n\n  constructor(fileName: string, snapshot: ts.IScriptSnapshot) {\n    this.fileName = fileName;\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * ファイルが更新されたときに呼ばれる\n   */\n  update(snapshot: ts.IScriptSnapshot): void {\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, this.fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * 仮想コードを生成するメイン処理\n   */\n  private generateVirtualCode(sourceContent: string): void {\n    const segments: CodeSegment[] = [];\n\n    // 1. script/scriptSetup のコードを生成\n    this.generateScriptCode(segments);\n\n    // 2. テンプレートの型チェックコードを生成\n    this.generateTemplateCode(segments);\n\n    // 3. セグメントから最終的なコードとマッピングを構築\n    const { code, mappings } = this.buildCode(segments, sourceContent);\n\n    // 4. 埋め込みコード（TypeScript）として登録\n    this.embeddedCodes = [\n      {\n        id: 'ts',\n        languageId: 'typescript',\n        snapshot: createScriptSnapshot(code),\n        mappings,\n        embeddedCodes: [],\n      },\n    ];\n  }\n\n  /**\n   * script/scriptSetup ブロックから TypeScript コードを生成\n   */\n  private generateScriptCode(segments: CodeSegment[]): void {\n    const { script, scriptSetup } = this.sfc;\n\n    if (scriptSetup) {\n      // <script setup> の内容をそのまま出力\n      // マッピング情報も追加（元ファイルの位置と対応付け）\n      segments.push([\n        scriptSetup.content,\n        scriptSetup.loc.start.offset,\n        scriptSetup.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    } else if (script) {\n      // <script> の内容をそのまま出力\n      segments.push([\n        script.content,\n        script.loc.start.offset,\n        script.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    }\n  }\n\n  /**\n   * テンプレート内の式を型チェックするためのコードを生成\n   */\n  private generateTemplateCode(segments: CodeSegment[]): void {\n    const { template } = this.sfc;\n    if (!template) return;\n\n    // テンプレート型チェック用のコードを追加\n    segments.push(['\\n// Template type-checking\\n']);\n    segments.push(['declare const __VLS_template: () => void;\\n']);\n\n    // マスタッシュ式 {{ expr }} を検出\n    const mustacheRegex = /\\{\\{\\s*([\\s\\S]*?)\\s*\\}\\}/g;\n    let match: RegExpExecArray | null;\n    let exprIndex = 0;\n\n    while ((match = mustacheRegex.exec(template.content)) !== null) {\n      const expr = match[1];\n      // 元ファイルでの式の位置を計算\n      const exprStartInTemplate = match.index + match[0].indexOf(expr);\n      const sourceStart = template.loc.start.offset + exprStartInTemplate;\n      const sourceEnd = sourceStart + expr.length;\n\n      // 式を検証するコードを生成\n      // (() => { const __VLS_expr0 = (message); })();\n      segments.push([`(() => {\\n  const __VLS_expr${exprIndex} = (`]);\n      segments.push([\n        expr,\n        sourceStart,\n        sourceEnd,\n        { verification: true },\n      ]);\n      segments.push([');\\n})();\\n']);\n\n      exprIndex++;\n    }\n  }\n\n  /**\n   * セグメントから最終的なコードとマッピングを構築\n   */\n  private buildCode(\n    segments: CodeSegment[],\n    sourceContent: string\n  ): { code: string; mappings: CodeMapping[] } {\n    let code = '';\n    const mappings: CodeMapping[] = [];\n\n    for (const segment of segments) {\n      const [text, sourceStart, sourceEnd, features] = segment;\n\n      if (sourceStart !== undefined && sourceEnd !== undefined) {\n        // マッピング情報がある場合，記録する\n        mappings.push({\n          sourceOffsets: [sourceStart],\n          generatedOffsets: [code.length],\n          lengths: [sourceEnd - sourceStart],\n          data: {\n            verification: features?.verification ?? false,\n            completion: true,\n            semantic: true,\n            navigation: true,\n            structure: true,\n            format: false,\n          },\n        });\n      }\n\n      code += text;\n    }\n\n    return { code, mappings };\n  }\n}\n\n/**\n * TypeScript のスクリプトスナップショットを作成\n */\nfunction createScriptSnapshot(content: string): ts.IScriptSnapshot {\n  return {\n    getText: (start, end) => content.slice(start, end),\n    getLength: () => content.length,\n    getChangeRange: () => undefined,\n  };\n}\n```\n\n### 言語プラグイン\n\nVolar.js に `.vue` ファイルの処理方法を伝えるプラグインを実装します．\n\n```ts\n// languagePlugin.ts\nimport type { LanguagePlugin } from '@volar/language-core';\nimport { ChibivueVirtualCode } from './virtualCode';\n\n/**\n * Volar.js 用の言語プラグインを作成\n *\n * このプラグインは以下の役割を担います：\n * 1. .vue ファイルを識別する\n * 2. .vue ファイルから仮想コードを生成する\n * 3. TypeScript 言語サービスに仮想コードを提供する\n */\nexport function createChibivueLanguagePlugin(): LanguagePlugin<\n  string,\n  ChibivueVirtualCode\n> {\n  return {\n    /**\n     * ファイル拡張子から言語 ID を判定\n     * .vue ファイルの場合 \"vue\" を返す\n     */\n    getLanguageId(scriptId: string): string | undefined {\n      if (scriptId.endsWith('.vue')) {\n        return 'vue';\n      }\n      return undefined;\n    },\n\n    /**\n     * 仮想コードを新規作成\n     * ファイルが初めて開かれたときに呼ばれる\n     */\n    createVirtualCode(scriptId, languageId, snapshot) {\n      if (languageId === 'vue') {\n        return new ChibivueVirtualCode(scriptId, snapshot);\n      }\n      return undefined;\n    },\n\n    /**\n     * 既存の仮想コードを更新\n     * ファイルが編集されたときに呼ばれる\n     */\n    updateVirtualCode(_scriptId, virtualCode, snapshot) {\n      virtualCode.update(snapshot);\n      return virtualCode;\n    },\n\n    /**\n     * TypeScript 固有の設定\n     */\n    typescript: {\n      /**\n       * TypeScript に .vue ファイルを認識させる設定\n       *\n       * - extension: 対象のファイル拡張子\n       * - isMixedContent: 複数の言語が含まれることを示す\n       * - scriptKind: TypeScript の ScriptKind\n       *   - 7 = Deferred（遅延評価，仮想コードを使用）\n       */\n      extraFileExtensions: [\n        { extension: 'vue', isMixedContent: true, scriptKind: 7 },\n      ],\n\n      /**\n       * 仮想コードから TypeScript に渡すスクリプトを取得\n       *\n       * @returns\n       *   - code: 埋め込みの TypeScript コード\n       *   - extension: \".ts\"（TypeScript として扱う）\n       *   - scriptKind: 3 = TS（通常の TypeScript）\n       */\n      getServiceScript(rootVirtualCode) {\n        for (const code of rootVirtualCode.embeddedCodes) {\n          if (code.id === 'ts') {\n            return {\n              code,\n              extension: '.ts',\n              scriptKind: 3, // ts.ScriptKind.TS\n            };\n          }\n        }\n        return undefined;\n      },\n    },\n  };\n}\n```\n\n### 言語サーバー\n\nLSP（Language Server Protocol）サーバーは，エディタと言語機能を橋渡しします．\n\n```ts\n// server.ts\nimport {\n  createConnection,\n  createServer,\n  createSimpleProjectProviderFactory,\n  loadTsdkByPath,\n} from '@volar/language-server/node';\nimport { create as createTypeScriptServices } from 'volar-service-typescript';\nimport { createChibivueLanguagePlugin } from '@chibivue/language-core';\n\n/**\n * LSP (Language Server Protocol) について\n *\n * LSP はエディタと言語機能を分離するためのプロトコルです．\n *\n * ┌──────────┐                        ┌──────────────────┐\n * │  VSCode  │ ◄───── LSP 通信 ─────► │  Language Server │\n * │  Neovim  │    (JSON-RPC over      │  (このファイル)  │\n * │  Emacs   │     stdio/IPC)         │                  │\n * └──────────┘                        └──────────────────┘\n *\n * LSP の主なリクエスト：\n * - textDocument/completion: 自動補完候補の取得\n * - textDocument/hover: ホバー情報の取得\n * - textDocument/definition: 定義へ移動\n * - textDocument/references: 参照の検索\n * - textDocument/rename: シンボルのリネーム\n * - textDocument/diagnostics: エラー診断\n */\n\n// LSP 接続を作成（stdin/stdout または IPC で通信）\nconst connection = createConnection();\n\n// Volar の言語サーバーを作成\nconst server = createServer(connection);\n\n// 接続のリスニングを開始\nconnection.listen();\n\n/**\n * 初期化リクエストのハンドラ\n * クライアント（エディタ）が接続したときに呼ばれる\n */\nconnection.onInitialize((params) => {\n  // TypeScript SDK のパスを取得（クライアントから渡される）\n  const tsdk = params.initializationOptions?.typescript?.tsdk;\n\n  // TypeScript モジュールをロード\n  const ts = tsdk\n    ? loadTsdkByPath(tsdk, params.locale)\n    : require('typescript');\n\n  // chibivue 言語プラグインを作成\n  const chibivuePlugin = createChibivueLanguagePlugin();\n\n  // サーバーを初期化して機能を登録\n  return server.initialize(\n    params,\n    // プロジェクト管理の設定（tsconfig.json の検出など）\n    createSimpleProjectProviderFactory(),\n    {\n      /**\n       * 言語プラグインを返す\n       * .vue ファイルの仮想コード生成を担当\n       */\n      getLanguagePlugins() {\n        return [chibivuePlugin];\n      },\n\n      /**\n       * サービスプラグインを返す\n       * TypeScript の言語機能（補完，診断など）を提供\n       */\n      getServicePlugins() {\n        return [...createTypeScriptServices(ts)];\n      },\n    }\n  );\n});\n\n/**\n * 初期化完了のハンドラ\n */\nconnection.onInitialized(() => {\n  // 必要に応じて追加の設定を行う\n});\n```\n\n### VSCode 拡張機能\n\nVSCode と言語サーバーを接続する拡張機能を実装します．\n\n```ts\n// extension.ts\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport {\n  LanguageClient,\n  LanguageClientOptions,\n  ServerOptions,\n  TransportKind,\n} from 'vscode-languageclient/node';\n\nlet client: LanguageClient | undefined;\n\n/**\n * 拡張機能のアクティベーション\n * .vue ファイルを開いたときに自動的に呼ばれる\n */\nexport async function activate(context: vscode.ExtensionContext) {\n  // 言語サーバーのパスを解決\n  const serverPath = context.asAbsolutePath(\n    path.join('dist', 'server.js')\n  );\n\n  // サーバーの起動オプション\n  const serverOptions: ServerOptions = {\n    run: {\n      module: serverPath,\n      transport: TransportKind.ipc, // IPC で通信\n    },\n    debug: {\n      module: serverPath,\n      transport: TransportKind.ipc,\n      options: { execArgv: ['--nolazy', '--inspect=6009'] },\n    },\n  };\n\n  // クライアントのオプション\n  const clientOptions: LanguageClientOptions = {\n    // どのファイルを処理するか\n    documentSelector: [{ scheme: 'file', language: 'vue' }],\n\n    // 初期化時にサーバーに渡すオプション\n    initializationOptions: {\n      typescript: {\n        // VSCode 内蔵の TypeScript SDK を使用\n        tsdk: path.join(\n          vscode.env.appRoot,\n          'extensions/node_modules/typescript/lib'\n        ),\n      },\n    },\n  };\n\n  // Language Client を作成\n  client = new LanguageClient(\n    'chibivue',                    // クライアント ID\n    'Chibivue Language Server',   // 表示名\n    serverOptions,\n    clientOptions\n  );\n\n  // 言語サーバーを起動\n  await client.start();\n\n  // 拡張機能の非アクティベーション時にクリーンアップ\n  context.subscriptions.push({\n    dispose: () => client?.stop(),\n  });\n}\n\n/**\n * 拡張機能の非アクティベーション\n */\nexport function deactivate(): Thenable<void> | undefined {\n  return client?.stop();\n}\n```\n\n### シンタックスハイライト（TextMate 文法）\n\nシンタックスハイライトは TextMate 文法で定義します．これは VSCode の組み込み機能を使用し，言語サーバーは関与しません．\n\n```json\n// syntaxes/vue.tmLanguage.json\n{\n  \"name\": \"Vue\",\n  \"scopeName\": \"source.vue\",\n  \"patterns\": [\n    { \"include\": \"#template\" },\n    { \"include\": \"#script\" },\n    { \"include\": \"#style\" }\n  ],\n  \"repository\": {\n    \"template\": {\n      \"begin\": \"(<)(template)\",\n      \"end\": \"(</)(template)(>)\",\n      \"patterns\": [{ \"include\": \"text.html.basic\" }]\n    },\n    \"script\": {\n      \"begin\": \"(<)(script)\",\n      \"end\": \"(</)(script)(>)\",\n      \"patterns\": [{ \"include\": \"source.ts\" }]\n    },\n    \"style\": {\n      \"begin\": \"(<)(style)\",\n      \"end\": \"(</)(style)(>)\",\n      \"patterns\": [{ \"include\": \"source.css\" }]\n    }\n  }\n}\n```\n\n## サポートされている機能\n\n| 機能                   | ステータス   | 説明                                           |\n| ---------------------- | ------------ | ---------------------------------------------- |\n| シンタックスハイライト | サポート済み | TextMate 文法による色分け                      |\n| 自動補完               | サポート済み | 変数，関数，プロパティの補完                   |\n| 型チェック             | サポート済み | TypeScript による型エラーの検出                |\n| 定義へ移動             | サポート済み | 変数や関数の定義位置へジャンプ                 |\n| エラー診断             | サポート済み | 構文エラー，型エラーの表示                     |\n| シンボルリネーム       | サポート済み | 変数名などの一括変更                           |\n| ホバー情報             | サポート済み | カーソル位置の型情報を表示                     |\n\n## まとめ\n\nLanguage Tools は `.vue` ファイルを仮想 TypeScript コードに変換することで，TypeScript の全機能を SFC で利用可能にします．\n\n**主要なコンポーネント：**\n\n1. **SFC パーサー** - `.vue` ファイルを template，script，style ブロックに分解\n2. **仮想コード生成** - SFC を TypeScript に変換し，コードマッピングを生成\n3. **言語プラグイン** - Volar.js のインターフェースを実装し，仮想コードを提供\n4. **言語サーバー** - LSP を通じてエディタと通信\n5. **VSCode 拡張機能** - VSCode と言語サーバーを接続\n\nこの実装は教育目的の最小限のものです．本番環境で使用される [vuejs/language-tools](https://github.com/vuejs/language-tools) では，以下のような高度な機能が追加されています：\n\n- テンプレート内のディレクティブ（`v-if`，`v-for` など）の型チェック\n- コンポーネント props の型検証\n- `<style scoped>` のセレクタ補完\n- `<template>` 内の HTML 補完\n- マクロ（`defineProps`，`defineEmits`）のサポート\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/020-ssr/010-create-ssr-app.md",
    "content": "# Server Side Rendering (SSR)\n\n## SSR とは\n\nServer Side Rendering (SSR) は，Vue.js アプリケーションをサーバー上で HTML 文字列にレンダリングし，クライアントに送信する技術です．これにより以下のメリットがあります：\n\n1. **SEO の向上**: 検索エンジンのクローラーが完全なコンテンツを取得できる\n2. **初期表示の高速化**: ブラウザは JavaScript の実行を待たずに HTML を表示できる\n3. **パフォーマンスの改善**: 特に低速なデバイスやネットワーク環境で効果的\n\n<KawaikoNote variant=\"question\" title=\"SSR と SPA の違い\">\n\n通常の SPA では，クライアントが空の HTML を受け取り，JavaScript で DOM を構築します．\nSSR では，サーバーで完成した HTML を送るので，ユーザーはすぐにコンテンツを見ることができます．\nJavaScript のダウンロード・実行を待たずにコンテンツが表示されるのがポイントです！\n\n</KawaikoNote>\n\n## パッケージ構成\n\nchibivue の SSR 実装は `@chibivue/server-renderer` パッケージで提供されています．\n\n```\npackages/server-renderer/src/\n├── index.ts\n├── renderToString.ts      # メインエントリーポイント\n├── render.ts              # VNode レンダリング\n└── helpers/\n    ├── ssrRenderAttrs.ts  # 属性のレンダリング\n    └── ssrUtils.ts        # ユーティリティ関数\n```\n\n## 型定義\n\n### SSRBuffer\n\nSSR では，レンダリング結果を効率的に構築するために `SSRBuffer` というデータ構造を使用します．\n\n```ts\n// packages/server-renderer/src/render.ts\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\n```\n\nバッファは以下を含むことができます：\n- **文字列**: HTML の一部\n- **ネストされたバッファ**: 子コンポーネントの結果\n- **Promise**: 非同期コンポーネントの結果\n\n### SSRContext\n\nSSR 時のコンテキスト情報を保持します．\n\n```ts\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  __watcherHandles?: (() => void)[];\n};\n```\n\n## renderToString の実装\n\n### メインエントリーポイント\n\n```ts\n// packages/server-renderer/src/renderToString.ts\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // VNode を直接渡された場合、ラッパーコンポーネントで包む\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // App インスタンスの場合\n  const app = input;\n  const vnode = createVNode(app._component, app._props);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  // watcher のクリーンアップ\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n```\n\n### バッファの展開\n\nネストされたバッファと Promise を再帰的に展開します．\n\n```ts\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  // 非同期要素がなければ同期的に処理\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    // Promise の場合は解決を待つ\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    // ネストされたバッファは再帰処理\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n```\n\n## createBuffer の実装\n\nバッファを効率的に構築するためのファクトリ関数です．\n\n```ts\n// packages/server-renderer/src/render.ts\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        // 連続する文字列は結合して最適化\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      // Promise や非同期バッファがあればフラグを立てる\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n```\n\nポイント：\n1. 連続する文字列は自動的に結合（メモリ効率化）\n2. `appendable` フラグで結合可能かを追跡\n3. 非同期要素があれば `hasAsync` フラグを設定\n\n<KawaikoNote variant=\"funny\" title=\"バッファの賢い工夫\">\n\n「こんにちは」「世界」という連続する文字列は，配列に別々に入れるより\n「こんにちは世界」と結合した方がメモリ効率が良いですよね．\n`appendable` フラグでこれを自動的にやってくれます！\n\n</KawaikoNote>\n\n## コンポーネントのレンダリング\n\n### renderComponentVNode\n\n```ts\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  // コンポーネントインスタンスを作成\n  const instance = (vnode.component = createComponentInstance(\n    vnode,\n    parentComponent,\n    null,\n  ));\n\n  // setup を実行\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  // 非同期 setup の場合は Promise を返す\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() =>\n      renderComponentSubTree(instance),\n    );\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n```\n\n### renderComponentSubTree\n\n```ts\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    // 関数コンポーネント\n    const root = comp(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    // render 関数を持つコンポーネント\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n```\n\n## VNode のレンダリング\n\n### renderVNode\n\n各種 VNode タイプに応じてレンダリングを行います．\n\n```ts\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  // ディレクティブの SSR 対応\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(\n        children\n          ? `<!--${escapeHtmlComment(children as string)}-->`\n          : `<!---->`,\n      );\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      } else if (shapeFlag & ShapeFlags.TELEPORT) {\n        renderTeleportVNode(push, vnode, parentComponent);\n      }\n  }\n}\n```\n\n### renderElementVNode\n\nHTML 要素を文字列にレンダリングします．\n\n```ts\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  // 属性をレンダリング\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  // void タグは閉じタグなし\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      // 特殊プロパティの処理\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n```\n\n### renderVNodeChildren\n\n子要素を順番にレンダリングします．\n\n```ts\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n```\n\n### renderTeleportVNode\n\nTeleport コンポーネントの SSR 対応です．\n\n```ts\nfunction renderTeleportVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const target = vnode.props && vnode.props.to;\n  const disabled = vnode.props && vnode.props.disabled;\n\n  if (!target) {\n    if (!disabled) {\n      console.warn(`Teleport is missing target prop.`);\n    }\n    return;\n  }\n\n  if (!isString(target)) {\n    console.warn(`Teleport target must be a query selector string.`);\n    return;\n  }\n\n  // disabled の場合はインラインでレンダリング\n  if (disabled) {\n    renderVNodeChildren(push, vnode.children as VNodeArrayChildren, parentComponent);\n  } else {\n    // enabled の場合はプレースホルダーコメントを挿入\n    push(`<!--teleport start-->`);\n    push(`<!--teleport end-->`);\n  }\n}\n```\n\n## 属性のレンダリング\n\n### ssrRenderAttrs\n\n```ts\n// packages/server-renderer/src/helpers/ssrRenderAttrs.ts\nexport function ssrRenderAttrs(\n  props: Record<string, unknown>,\n  tag?: string,\n): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (\n      ssrIsIgnoredKey(key) ||\n      isOn(key) ||\n      (tag === \"textarea\" && key === \"value\")\n    ) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return (\n    key === \"key\" ||\n    key === \"ref\" ||\n    key === \"innerHTML\" ||\n    key === \"textContent\"\n  );\n}\n```\n\n### ssrRenderDynamicAttr\n\n動的な属性をレンダリングします．\n\n```ts\nexport function ssrRenderDynamicAttr(\n  key: string,\n  value: unknown,\n  tag?: string,\n): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n\n  // カスタム要素や SVG ではそのまま、それ以外は変換\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag))\n      ? key\n      : propsToAttrMap[key] || key.toLowerCase();\n\n  // boolean 属性の処理\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\"\n      ? ` ${attrKey}`\n      : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(\n      `[@chibivue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`,\n    );\n    return \"\";\n  }\n}\n```\n\n### class と style のレンダリング\n\n```ts\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(\n  styles: Record<string, string | number> | null,\n): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n```\n\n## ディレクティブの SSR 対応\n\n```ts\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const { dir: { getSSRProps } } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n```\n\nディレクティブが `getSSRProps` を実装していれば，その結果を props にマージします．\n\n## エスケープ処理\n\n<KawaikoNote variant=\"warning\" title=\"セキュリティは超重要！\">\n\nSSR ではユーザー入力がそのまま HTML に出力される可能性があります．\nエスケープ処理を怠ると，XSS（クロスサイトスクリプティング）攻撃の標的になってしまいます．\n`escapeHtml` は SSR において必須のセキュリティ対策です！\n\n</KawaikoNote>\n\nXSS を防ぐための HTML エスケープです．\n\n```ts\n// packages/server-renderer/src/helpers/ssrUtils.ts\nconst escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n```\n\n## 使用例\n\n```ts\nimport { createApp } from \"@chibivue/runtime-dom\";\nimport { renderToString } from \"@chibivue/server-renderer\";\n\nconst App = {\n  setup() {\n    return { message: \"Hello SSR!\" };\n  },\n  template: `<div>{{ message }}</div>`,\n};\n\nconst app = createApp(App);\n\n// サーバーサイドでレンダリング\nconst html = await renderToString(app);\nconsole.log(html); // <div>Hello SSR!</div>\n```\n\n## 処理フロー\n\n```\nrenderToString(app)\n  ↓\ncreateVNode(app._component, app._props)\n  ↓\nrenderComponentVNode(vnode)\n  ├── createComponentInstance()\n  ├── setupComponent()\n  └── renderComponentSubTree()\n      ├── createBuffer()\n      ├── instance.render() or comp()\n      └── renderVNode(push, root, instance)\n          ├── Text → escapeHtml(children)\n          ├── Comment → <!--...-->\n          ├── Fragment → <!--[--> ... <!--]-->\n          ├── Element → renderElementVNode()\n          │   ├── <tag + ssrRenderAttrs(props) + >\n          │   ├── children の処理\n          │   └── </tag>\n          └── Component → renderComponentVNode() (再帰)\n  ↓\nunrollBuffer(buffer)\n  ↓\nHTML 文字列\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR の基本が完成！\">\n\n`renderToString` で VNode を HTML 文字列に変換できるようになりました．\n次のセクションで学ぶ hydration と組み合わせると，\nSSR の恩恵を受けながらインタラクティブな SPA を実現できます！\n\n</KawaikoNote>\n\n## まとめ\n\nchibivue の SSR 実装は以下の要素で構成されています：\n\n1. **SSRBuffer**: 効率的な文字列構築のためのバッファシステム（文字列の自動結合，非同期対応）\n2. **renderComponentVNode**: コンポーネントの VNode を HTML に変換（非同期 setup 対応）\n3. **renderVNode**: 各種 VNode タイプに応じたレンダリング分岐\n4. **renderElementVNode**: HTML 要素の文字列化（void タグ，特殊プロパティ対応）\n5. **ssrRenderAttrs**: 属性のレンダリング（class/style 正規化，boolean 属性，安全性チェック）\n6. **エスケープ処理**: XSS 対策のための HTML エスケープ\n7. **ディレクティブ対応**: `getSSRProps` による SSR 時のプロパティ注入\n\n次のセクションでは，SSR で生成された HTML をクライアントサイドで「復元」する hydration について学びます．\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/010_ssr)\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/020-ssr/020-hydration.md",
    "content": "# Hydration（ハイドレーション）\n\n## Hydration とは\n\n前章で，`renderToString` を使って Vue コンポーネントを HTML 文字列にレンダリングする方法を学びました．しかし，SSR で生成された HTML は単なる静的なマークアップであり，イベントハンドラーやリアクティビティは機能しません．\n\nHydration（ハイドレーション）は，サーバーで生成された HTML を「活性化」し，クライアントサイドの Vue アプリケーションとして機能させるプロセスです．\n\n<KawaikoNote variant=\"question\" title=\"なぜ「水分補給」？\">\n\nHydration（水分補給）という名前は，静的な HTML に「命を吹き込む」イメージから来ています．\n乾燥した植物に水を与えると生き生きとするように，静的な HTML にイベントハンドラーやリアクティビティを注入します．\n\n</KawaikoNote>\n\n## 通常のマウントとの違い\n\n### 通常の `createApp`\n\n```\n1. VNode を生成\n2. DOM 要素を新規作成\n3. DOM をコンテナに挿入\n```\n\n### `createSSRApp`（Hydration）\n\n```\n1. VNode を生成\n2. 既存の DOM 要素を走査\n3. VNode と DOM 要素を関連付け\n4. イベントハンドラーをアタッチ\n```\n\n<KawaikoNote variant=\"funny\" title=\"Hydration の本質\">\n\nHydration は「DOM を作らない render」とも言えます．\n既存の DOM があるので，それを VNode と関連付けるだけで良いのです．\n\n</KawaikoNote>\n\n## 型定義\n\n### HydrateOptions\n\nHydration に必要なオプションを定義します．\n\n```ts\n// runtime-core/hydration.ts\nexport interface HydrateOptions {\n  patchProp: (el: Element, key: string, prevValue: any, nextValue: any) => void;\n  nextSibling: (node: Node) => Node | null;\n}\n```\n\n- `patchProp`: プロパティ（特にイベントハンドラー）を DOM 要素にアタッチするための関数\n- `nextSibling`: DOM ツリーを走査するための関数\n\n## createHydrationRenderer の実装\n\n### 基本構造\n\n```ts\n// runtime-core/hydration.ts\nexport function createHydrationRenderer(options: HydrateOptions) {\n  const { patchProp, nextSibling } = options;\n\n  function hydrate(vnode: VNode, container: Element): void {\n    const node = container.firstChild;\n    if (node) {\n      hydrateNode(node, vnode, null);\n    }\n  }\n\n  // ... その他の関数\n\n  return { hydrate };\n}\n```\n\n`hydrate` 関数は，コンテナの最初の子ノードから始めて，VNode ツリーと DOM ツリーを並行して走査します．\n\n### hydrateNode - ノードの種類による分岐\n\n```ts\nfunction hydrateNode(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  const { type, shapeFlag } = vnode;\n\n  // 重要: VNode と DOM 要素を関連付け\n  vnode.el = node;\n\n  if (type === Text) {\n    // テキストノード: 次の兄弟を返す\n    return nextSibling(node);\n  } else if (type === Comment) {\n    // コメントノード: 次の兄弟を返す\n    return nextSibling(node);\n  } else if (type === Fragment) {\n    // Fragment: 特別な処理\n    return hydrateFragment(node, vnode, parentComponent);\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    // HTML 要素: 子要素も処理\n    return hydrateElement(node as Element, vnode, parentComponent);\n  }\n\n  return nextSibling(node);\n}\n```\n\nポイント：\n- `vnode.el = node` が最も重要な処理．これにより，後続の更新で VNode が正しい DOM 要素を参照できます\n- 各関数は「次に処理すべき DOM ノード」を返します\n\n### hydrateElement - HTML 要素のハイドレーション\n\n```ts\nfunction hydrateElement(\n  el: Element,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  vnode.el = el;\n\n  const { props, children, shapeFlag } = vnode;\n\n  // イベントハンドラーをアタッチ\n  if (props) {\n    for (const key in props) {\n      if (key.startsWith(\"on\") && typeof props[key] === \"function\") {\n        patchProp(el, key, null, props[key]);\n      }\n    }\n  }\n\n  // 子要素のハイドレーション\n  if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n    hydrateChildren(el.firstChild, children as VNode[], parentComponent);\n  }\n\n  return nextSibling(el);\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"イベントハンドラーのみをアタッチ\">\n\nHydration 時に処理するのはイベントハンドラー（`on` で始まる props）だけです．\n`class` や `style` などの属性は既に SSR で HTML に含まれているため，アタッチ不要です．\n\n</KawaikoNote>\n\n### hydrateChildren - 子要素の処理\n\n```ts\nfunction hydrateChildren(\n  node: Node | null,\n  children: VNode[],\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  for (let i = 0; i < children.length; i++) {\n    const child = normalizeVNode(children[i]);\n    if (node) {\n      node = hydrateNode(node, child, parentComponent);\n    }\n  }\n  return node;\n}\n```\n\nVNode の children と DOM の子ノードを順番に処理していきます．各 `hydrateNode` は次の兄弟ノードを返すので，それを使って走査を続けます．\n\n### hydrateFragment - Fragment の処理\n\nSSR では Fragment は `<!--[-->` と `<!--]-->` というコメントノードで囲まれてレンダリングされます．\n\n```ts\nfunction hydrateFragment(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  // 開始コメント（<!--[-->）を el に設定\n  vnode.el = node;\n\n  // 開始コメントの次から子要素が始まる\n  let current = nextSibling(node);\n  const children = vnode.children as VNode[];\n\n  if (children && children.length > 0) {\n    current = hydrateChildren(current, children, parentComponent);\n  }\n\n  // 終了コメント（<!--]-->）を anchor に設定\n  vnode.anchor = current;\n  return current ? nextSibling(current) : null;\n}\n```\n\n```html\n<!-- SSR 出力例 -->\n<!--[-->\n<p>Item 1</p>\n<p>Item 2</p>\n<p>Item 3</p>\n<!--]-->\n```\n\n## createSSRApp の実装\n\n`createSSRApp` は通常の `createApp` とほぼ同じですが，mount 時に Hydration を行います．\n\n```ts\n// runtime-dom/index.ts\n\n// Hydration レンダラーを作成\nconst { hydrate: hydrateVNode } = createHydrationRenderer({\n  patchProp,\n  nextSibling: nodeOps.nextSibling,\n});\n\nexport const createSSRApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n\n    // コンテナに SSR コンテンツがあるかチェック\n    if (container.hasChildNodes()) {\n      // Hydration を実行\n      const proxy = mount(container, true /* isHydrate */);\n      return proxy;\n    } else {\n      // SSR コンテンツがなければ通常のマウント\n      mount(container);\n    }\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n```\n\n## 処理フロー\n\n```\n[サーバー側]\nrenderToString(app)\n  ↓\n<div id=\"app\">\n  <button>Count: 0</button>\n</div>\n\n[クライアント側]\ncreateSSRApp(App).mount('#app')\n  ↓\ncontainer.hasChildNodes() → true\n  ↓\nhydrate(vnode, container)\n  ↓\nhydrateNode(button, vnode)\n  ├── vnode.el = button  ← VNode と DOM を関連付け\n  └── patchProp(button, 'onClick', null, handler)  ← イベントをアタッチ\n  ↓\nボタンをクリックするとリアクティビティが動作\n```\n\n## 使用例\n\n### サーバーサイド\n\n```ts\n// server.ts\nimport { createApp } from '@chibivue/runtime-dom'\nimport { renderToString } from '@chibivue/server-renderer'\nimport App from './App.vue'\n\nconst app = createApp(App)\nconst html = await renderToString(app)\n\n// HTML をクライアントに送信\nres.send(`\n  <!DOCTYPE html>\n  <html>\n    <body>\n      <div id=\"app\">${html}</div>\n      <script src=\"/client.js\"></script>\n    </body>\n  </html>\n`)\n```\n\n### クライアントサイド\n\n```ts\n// client.ts\nimport { createSSRApp } from '@chibivue/runtime-dom'\nimport App from './App.vue'\n\n// createSSRApp を使用（createApp ではなく）\nconst app = createSSRApp(App)\napp.mount('#app')\n```\n\n### App コンポーネント\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { ref } from '@chibivue/runtime-core'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n\n<template>\n  <button @click=\"increment\">Count: {{ count }}</button>\n</template>\n```\n\n## Hydration ミスマッチ\n\nHydration では，SSR で生成された HTML と，クライアントで生成される VNode が一致している必要があります．一致しない場合，「Hydration ミスマッチ」が発生します．\n\n### よくある原因\n\n1. **日時・乱数**: `new Date()` や `Math.random()` はサーバーとクライアントで異なる値になる\n2. **ブラウザ固有の API**: `window` や `localStorage` はサーバーでは存在しない\n3. **条件分岐**: サーバーとクライアントで異なるパスを通る\n\n### 対策\n\n```vue\n<script setup>\nimport { ref, onMounted } from '@chibivue/runtime-core'\n\n// サーバーとクライアントで同じ初期値\nconst clientOnly = ref(false)\n\n// クライアント側でのみ更新\nonMounted(() => {\n  clientOnly.value = true\n})\n</script>\n\n<template>\n  <div v-if=\"clientOnly\">\n    This content is only shown on client\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"ミスマッチに注意！\">\n\nHydration ミスマッチが発生すると，Vue は警告を出し，最悪の場合は DOM が壊れます．\nサーバーとクライアントで同じ出力になるよう注意しましょう．\n\n</KawaikoNote>\n\n## 今後の拡張\n\n現在の実装は最小限ですが，Vue 本家には以下のような機能があります：\n\n1. **Hydration ミスマッチの検出**: 開発モードでサーバー/クライアントの不一致を検出\n2. **Partial Hydration**: 必要な部分だけを Hydration（パフォーマンス最適化）\n3. **PatchFlags を使った最適化**: 静的なノードは Hydration をスキップ\n4. **非同期コンポーネントの Hydration**: `Suspense` との連携\n\n<KawaikoNote variant=\"surprise\" title=\"Hydration 完了！\">\n\nこれで SSR の最後のピースが揃いました．\n`renderToString` でサーバーサイドレンダリングし，\n`createSSRApp` で Hydration することで，\n完全な SSR アプリケーションを実現できます．\n\n</KawaikoNote>\n\n## まとめ\n\nHydration の実装は以下の要素で構成されています：\n\n1. **createHydrationRenderer**: Hydration 用のレンダラーを作成\n2. **hydrateNode**: VNode の種類に応じた処理の分岐\n3. **hydrateElement**: HTML 要素とイベントハンドラーのアタッチ\n4. **hydrateChildren**: 子要素の再帰的な処理\n5. **hydrateFragment**: Fragment（コメントノードで囲まれた領域）の処理\n6. **createSSRApp**: Hydration 対応のアプリケーションファクトリ\n\nHydration の本質は「既存の DOM を作り直さずに VNode と関連付ける」ことです．これにより，SSR の高速な初期表示と，SPA のリッチなインタラクティビティを両立できます．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/020-ssr/030-compiler-ssr.md",
    "content": "# Compiler SSR\n\n## SSR コンパイラとは\n\nSSR コンパイラ（`@chibivue/compiler-ssr`）は，テンプレートを SSR に最適化されたコードにコンパイルするパッケージです．\n\n通常のクライアントサイドコンパイルでは VNode を生成するコードを出力しますが，SSR コンパイラは直接 HTML 文字列を生成するコードを出力します．これにより，サーバーサイドでのレンダリング効率が向上します．\n\n<KawaikoNote variant=\"base\" title=\"クライアントと SSR の違い\">\n\nクライアントサイドでは:\n```js\n// VNode を返す\nreturn _createElementVNode(\"div\", { class: \"hello\" }, \"Hello\")\n```\n\nSSR では:\n```js\n// 直接 HTML 文字列を push\n_push(`<div class=\"hello\">Hello</div>`)\n```\n\nSSR では VNode を経由せず直接文字列を生成するので効率的です！\n\n</KawaikoNote>\n\n## パッケージ構成\n\n```\npackages/compiler-ssr/src/\n├── index.ts                    # メインエントリーポイント\n├── runtimeHelpers.ts           # SSR ヘルパー関数の定義\n├── ssrCodegenTransform.ts      # SSR コード生成変換\n└── transforms/\n    ├── ssrTransformElement.ts   # 要素の変換\n    ├── ssrTransformComponent.ts # コンポーネントの変換\n    ├── ssrVIf.ts               # v-if の変換\n    └── ssrVFor.ts              # v-for の変換\n```\n\n## コンパイルの流れ\n\nSSR コンパイルは以下の手順で行われます：\n\n1. **パース**: テンプレートを AST に変換（`@chibivue/compiler-dom` の `parse` を使用）\n2. **変換**: SSR 用の NodeTransform を適用\n3. **SSR Codegen Transform**: AST を SSR 用のコード生成ノードに変換\n4. **コード生成**: 最終的な JavaScript コードを生成\n\n```ts\n// packages/compiler-ssr/src/index.ts\nexport function compile(source: string | RootNode, options: CompilerOptions = {}): CodegenResult {\n  const ast = typeof source === \"string\" ? baseParse(source, options) : source;\n\n  transform(ast, {\n    ...options,\n    nodeTransforms: [\n      ssrTransformIf,\n      ssrTransformFor,\n      transformExpression,\n      ssrTransformElement,\n      ssrTransformComponent,\n      ...(options.nodeTransforms || []),\n    ],\n  });\n\n  // テンプレート AST を SSR 用のコード生成 AST に変換\n  ssrCodegenTransform(ast, options);\n\n  return generate(ast, options);\n}\n```\n\n## SSR Transform Context\n\nSSR 変換で使用されるコンテキストです．\n\n```ts\n// packages/compiler-ssr/src/ssrCodegenTransform.ts\nexport interface SSRTransformContext {\n  root: RootNode;\n  options: CompilerOptions;\n  body: (JSChildNode | IfStatement)[];\n  helpers: Set<symbol>;\n  onError: (error: Error) => void;\n  helper<T extends symbol>(name: T): T;\n  pushStringPart(part: TemplateLiteral[\"elements\"][0]): void;\n  pushStatement(statement: IfStatement | CallExpression): void;\n}\n```\n\n### pushStringPart\n\n文字列パートをバッファに追加します．連続する文字列は自動的に結合されます．\n\n```ts\npushStringPart(part) {\n  if (!currentString) {\n    const currentCall = createCallExpression(`_push`);\n    body.push(currentCall);\n    currentString = createTemplateLiteral([]);\n    currentCall.arguments.push(currentString);\n  }\n  const bufferedElements = currentString.elements;\n  const lastItem = bufferedElements[bufferedElements.length - 1];\n  if (isString(part) && isString(lastItem)) {\n    // 連続する文字列は結合\n    bufferedElements[bufferedElements.length - 1] += part;\n  } else {\n    bufferedElements.push(part);\n  }\n}\n```\n\n### pushStatement\n\n制御フロー文（if/for）をバッファに追加します．\n\n```ts\npushStatement(statement) {\n  // 現在の文字列バッファを閉じる\n  currentString = null;\n  body.push(statement);\n}\n```\n\n## 要素の変換\n\n### ssrTransformElement\n\nHTML 要素を SSR 用のコードに変換します．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformElement.ts\nexport const ssrTransformElement: NodeTransform = (node, context) => {\n  if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.ELEMENT) {\n    return;\n  }\n\n  return function ssrPostTransformElement() {\n    const openTag: TemplateLiteral[\"elements\"] = [`<${node.tag}`];\n\n    // 属性の処理\n    for (const prop of node.props) {\n      if (prop.type === NodeTypes.ATTRIBUTE) {\n        openTag.push(` ${prop.name}=\"${escapeHtml(prop.value.content)}\"`);\n      } else if (prop.type === NodeTypes.DIRECTIVE) {\n        // v-bind の処理\n        if (prop.name === \"bind\" && prop.arg && prop.exp) {\n          // class, style, その他の属性を処理\n        }\n      }\n    }\n\n    node.ssrCodegenNode = createTemplateLiteral(openTag);\n  };\n};\n```\n\n#### 属性のバインディング\n\n- **静的属性**: 直接文字列として出力\n- **v-bind:class**: `ssrRenderClass` ヘルパーを使用\n- **v-bind:style**: `ssrRenderStyle` ヘルパーを使用\n- **その他の動的属性**: `ssrRenderAttr` または `ssrRenderDynamicAttr` を使用\n\n### ssrProcessElement\n\n変換後の要素を処理してコードを生成します．\n\n```ts\nexport function ssrProcessElement(node: PlainElementNode, context: SSRTransformContext): void {\n  // 開始タグを出力\n  for (const element of node.ssrCodegenNode!.elements) {\n    context.pushStringPart(element);\n  }\n  context.pushStringPart(`>`);\n\n  // v-html の処理\n  const vHtml = node.props.find(p => p.type === NodeTypes.DIRECTIVE && p.name === \"html\");\n  if (vHtml && vHtml.exp) {\n    context.pushStringPart(vHtml.exp);\n  } else if (node.children.length) {\n    processChildren(node, context);\n  }\n\n  // 閉じタグ（void 要素以外）\n  if (!isVoidTag(node.tag)) {\n    context.pushStringPart(`</${node.tag}>`);\n  }\n}\n```\n\n## コンポーネントの変換\n\nコンポーネントは実行時に `ssrRenderComponent` を通じてレンダリングされます．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformComponent.ts\nexport function ssrProcessComponent(\n  node: ComponentNode,\n  context: SSRTransformContext,\n  parent: { children: any[] },\n): void {\n  const vnodeCall = createCallExpression(context.helper(SSR_RENDER_VNODE), [\n    `_push`,\n    createCallExpression(context.helper(SSR_RENDER_COMPONENT), [\n      createSimpleExpression(`_component_${node.tag}`, false),\n      // props\n      node.props.length ? /* props オブジェクト */ : createSimpleExpression(`null`, false),\n      // slots\n      createSimpleExpression(`null`, false),\n      // parent component\n      `_parent`,\n    ]),\n    `_parent`,\n  ]);\n\n  context.pushStatement(vnodeCall);\n}\n```\n\n## v-if の変換\n\nv-if は JavaScript の if 文に変換されます．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVIf.ts\nexport function ssrProcessIf(node: IfNode, context: SSRTransformContext): void {\n  const [rootBranch] = node.branches;\n  const ifStatement = createIfStatement(\n    rootBranch.condition!,\n    processIfBranch(rootBranch, context),\n  );\n  context.pushStatement(ifStatement);\n\n  let currentIf = ifStatement;\n  for (let i = 1; i < node.branches.length; i++) {\n    const branch = node.branches[i];\n    const branchBlockStatement = processIfBranch(branch, context);\n    if (branch.condition) {\n      // else-if\n      currentIf = currentIf.alternate = createIfStatement(branch.condition, branchBlockStatement);\n    } else {\n      // else\n      currentIf.alternate = branchBlockStatement;\n    }\n  }\n\n  // else がない場合は空コメントを出力\n  if (!currentIf.alternate) {\n    currentIf.alternate = createBlockStatement([createCallExpression(`_push`, [\"`<!---->`\"])]);\n  }\n}\n```\n\n入力:\n```html\n<div v-if=\"show\">Visible</div>\n<div v-else>Hidden</div>\n```\n\n出力:\n```js\nif (show) {\n  _push(`<div>Visible</div>`)\n} else {\n  _push(`<div>Hidden</div>`)\n}\n```\n\n## v-for の変換\n\nv-for は `ssrRenderList` ヘルパーを使用して変換されます．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVFor.ts\nexport function ssrProcessFor(node: ForNode, context: SSRTransformContext): void {\n  const renderLoop = createFunctionExpression(createForLoopParams(node.parseResult));\n  renderLoop.body = processChildrenAsStatement(node, context);\n\n  // フラグメントマーカー\n  context.pushStringPart(`<!--[-->`);\n  context.pushStatement(\n    createCallExpression(context.helper(SSR_RENDER_LIST), [node.source, renderLoop]),\n  );\n  context.pushStringPart(`<!--]-->`);\n}\n```\n\n入力:\n```html\n<div v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</div>\n```\n\n出力:\n```js\n_push(`<!--[-->`)\n_ssrRenderList(items, (item) => {\n  _push(`<div>${_ssrInterpolate(item.name)}</div>`)\n})\n_push(`<!--]-->`)\n```\n\n## SSR ヘルパー\n\nSSR コンパイラは以下のランタイムヘルパーを使用します．これらは `@chibivue/server-renderer` から提供されます．\n\n```ts\n// packages/compiler-ssr/src/runtimeHelpers.ts\nexport const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`);\nexport const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`);\nexport const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`);\nexport const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`);\nexport const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`);\nexport const SSR_RENDER_DYNAMIC_ATTR: unique symbol = Symbol(`ssrRenderDynamicAttr`);\nexport const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`);\nexport const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(`ssrIncludeBooleanAttr`);\nexport const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`);\nexport const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`);\n```\n\n### ヘルパーの役割\n\n| ヘルパー | 役割 |\n|---------|------|\n| `ssrInterpolate` | テキスト補間のエスケープ |\n| `ssrRenderAttrs` | オブジェクト形式の属性をレンダリング |\n| `ssrRenderClass` | class のレンダリング |\n| `ssrRenderStyle` | style のレンダリング |\n| `ssrRenderList` | v-for のイテレーション |\n| `ssrRenderComponent` | コンポーネントの VNode 作成 |\n| `ssrRenderVNode` | VNode を HTML 文字列に変換 |\n\n## SFC との統合\n\ncompiler-sfc は SSR モードでのコンパイルをサポートしています．\n\n```ts\n// packages/compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  compiler,\n  compilerOptions,\n  id,\n  scoped,\n  ssr = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = (compiler || defaultCompiler).compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n  return { code, ast, source, preamble };\n}\n```\n\n`ssr: true` を指定すると，自動的に SSR コンパイラが使用されます．\n\n## 生成されるコード例\n\n入力テンプレート:\n```html\n<div class=\"container\">\n  <h1>{{ title }}</h1>\n  <ul>\n    <li v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</li>\n  </ul>\n</div>\n```\n\n生成されるコード:\n```js\nimport { ssrInterpolate as _ssrInterpolate, ssrRenderList as _ssrRenderList } from 'chibivue/server-renderer'\n\nfunction ssrRender(_ctx, _push, _parent, _attrs) {\n  _push(`<div class=\"container\"><h1>${_ssrInterpolate(_ctx.title)}</h1><ul><!--[-->`)\n  _ssrRenderList(_ctx.items, (item) => {\n    _push(`<li>${_ssrInterpolate(item.name)}</li>`)\n  })\n  _push(`<!--]--></ul></div>`)\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR コンパイラの利点\">\n\nSSR コンパイラを使うと:\n- VNode のオーバーヘッドがない\n- テンプレートリテラルで効率的に文字列を生成\n- 静的な部分は直接文字列として出力される\n\nこれらにより，サーバーサイドでのレンダリングパフォーマンスが向上します！\n\n</KawaikoNote>\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/030-builtins/010-keep-alive.md",
    "content": "# KeepAlive\n\n## KeepAlive とは\n\n`<KeepAlive>` は，コンポーネントのインスタンスを破棄せずにキャッシュして再利用するための組み込みコンポーネントです．通常，コンポーネントが切り替わると古いコンポーネントはアンマウントされ，状態が失われますが，KeepAlive を使用することでコンポーネントの状態を保持したまま表示を切り替えることができます．\n\n<KawaikoNote variant=\"question\" title=\"なぜ KeepAlive が必要？\">\n\n例えばタブ切り替えのある画面で，入力中のフォームがあるタブから別のタブに移動して戻ってきたとき，\n入力内容が消えてしまったら困りますよね．KeepAlive はそんな「状態を保持したい」というニーズに応えます！\n\n</KawaikoNote>\n\n主なユースケース：\n\n1. **タブ切り替え**: フォーム入力中にタブを切り替えても入力内容を保持\n2. **ルーティング**: ページ遷移時にスクロール位置や入力状態を保持\n3. **パフォーマンス**: 頻繁に切り替わるコンポーネントの再レンダリングを回避\n\n## 基本的な使い方\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentTab\" />\n  </KeepAlive>\n</template>\n```\n\n## 実装の概要\n\n### Props の定義\n\n```ts\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n```\n\n- **include**: キャッシュ対象のコンポーネント名（含まれるもののみキャッシュ）\n- **exclude**: キャッシュ対象外のコンポーネント名（含まれるものはキャッシュしない）\n- **max**: キャッシュする最大数（LRU アルゴリズムで古いものから削除）\n\n### KeepAliveContext\n\nKeepAlive コンポーネントは，レンダラーとやり取りするための特別なコンテキストを持ちます．\n\n```ts\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n```\n\n- **activate**: キャッシュされたコンポーネントを表示に戻す\n- **deactivate**: コンポーネントを非表示にしてキャッシュする\n\n## コアロジックの実装\n\n### キャッシュの管理\n\n```ts\nconst cache: Map<any, VNode> = new Map();\nconst keys: Set<any> = new Set();\nlet current: VNode | null = null;\n\n// 非アクティブなコンポーネントを格納するための隠しコンテナ\nconst storageContainer = instance.renderer.o.createElement(\"div\");\n```\n\nKeepAlive は，`cache` Map を使ってコンポーネントの VNode をキャッシュします．`keys` Set は LRU（Least Recently Used）アルゴリズムのための順序管理に使用されます．\n\n### activate 関数\n\nキャッシュからコンポーネントを復元して表示します．\n\n```ts\ninstance.activate = (vnode, container, anchor, _parentComponent) => {\n  const instance = vnode.component!;\n  // 隠しコンテナから実際のコンテナへ移動\n  move(vnode, container, anchor);\n  // props の変更があれば反映\n  patch(instance.vnode, vnode, container, anchor, parentComponent);\n  queuePostFlushCb(() => {\n    instance.isDeactivated = false;\n    // onActivated フックを呼び出す\n    if (instance.a) {\n      instance.a.forEach((hook: () => void) => hook());\n    }\n  });\n};\n```\n\nポイント：\n1. 隠しコンテナからターゲットコンテナへ DOM を移動\n2. props の変更を patch で適用\n3. `onActivated` ライフサイクルフックを呼び出し\n\n### deactivate 関数\n\nコンポーネントを非表示にしてキャッシュします．\n\n```ts\ninstance.deactivate = (vnode: VNode) => {\n  // 隠しコンテナへ移動（DOM は削除されない）\n  move(vnode, storageContainer, null);\n  queuePostFlushCb(() => {\n    const instance = vnode.component!;\n    // onDeactivated フックを呼び出す\n    if (instance.da) {\n      instance.da.forEach((hook: () => void) => hook());\n    }\n    instance.isDeactivated = true;\n  });\n};\n```\n\n通常のアンマウントと異なり，DOM 要素は削除されず隠しコンテナに移動されるだけです．\n\n<KawaikoNote variant=\"funny\" title=\"隠しコンテナのトリック\">\n\n非表示にするコンポーネントは画面外の「隠れ家」に移動させておきます．\n必要になったら「隠れ家」から取り出すだけなので，再構築の手間が省けます！\n\n</KawaikoNote>\n\n### render 関数\n\nKeepAlive の核となるロジックです．\n\n```ts\nreturn (): VNode | undefined => {\n  if (!slots.default) {\n    return undefined;\n  }\n\n  const children = slots.default();\n  const rawVNode = children[0];\n\n  // 複数の子がある場合はキャッシュしない\n  if (children.length > 1) {\n    current = null;\n    return children as unknown as VNode;\n  }\n\n  // コンポーネントでない場合はそのまま返す\n  if (\n    !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n    !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n  ) {\n    current = null;\n    return rawVNode;\n  }\n\n  let vnode = rawVNode;\n  const comp = vnode.type as any;\n  const name = getComponentName(comp);\n  const { include, exclude, max } = props;\n\n  // include/exclude のフィルタリング\n  if (\n    (include && (!name || !matches(include, name))) ||\n    (exclude && name && matches(exclude, name))\n  ) {\n    current = vnode;\n    return rawVNode;\n  }\n\n  // キャッシュキーの決定\n  const key = vnode.key == null ? comp : vnode.key;\n  const cachedVNode = cache.get(key);\n\n  if (cachedVNode) {\n    // キャッシュがある場合：状態を復元\n    vnode.el = cachedVNode.el;\n    vnode.component = cachedVNode.component;\n    vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n    // LRU: 最近使用したので順序を更新\n    keys.delete(key);\n    keys.add(key);\n  } else {\n    // 新規キャッシュ\n    keys.add(key);\n    // max を超えたら最も古いものを削除\n    if (max && keys.size > parseInt(max as string, 10)) {\n      pruneCacheEntry(keys.values().next().value);\n    }\n  }\n\n  // フラグを設定してレンダラーに KeepAlive を認識させる\n  vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  current = vnode;\n  return vnode;\n};\n```\n\n### ShapeFlags による制御\n\nKeepAlive は ShapeFlags を使用してレンダラーと連携します．\n\n```ts\n// このコンポーネントは KeepAlive で管理されるべき\nvnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n\n// このコンポーネントはキャッシュから復元された\nvnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n```\n\nレンダラーはこれらのフラグを見て，通常のマウント/アンマウントの代わりに activate/deactivate を呼び出します．\n\n### include/exclude のマッチング\n\n```ts\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n```\n\nパターンは以下の形式をサポート：\n- 文字列（カンマ区切り）: `\"ComponentA,ComponentB\"`\n- 正規表現: `/^Tab/`\n- 配列: `[\"ComponentA\", /^Tab/]`\n\n### キャッシュのプルーニング\n\n```ts\nfunction pruneCacheEntry(key: any): void {\n  const cached = cache.get(key) as VNode;\n  // 現在表示中でなければアンマウント\n  if (!current || !isSameVNodeType(cached, current)) {\n    unmount(cached);\n  } else if (current) {\n    // 表示中の場合はフラグのみリセット\n    resetShapeFlag(current);\n  }\n  cache.delete(key);\n  keys.delete(key);\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n```\n\n## ライフサイクルフック\n\nKeepAlive で管理されるコンポーネントは，追加のライフサイクルフックを使用できます：\n\n- **onActivated**: コンポーネントがアクティブになったとき\n- **onDeactivated**: コンポーネントが非アクティブになったとき\n\n```ts\nimport { onActivated, onDeactivated } from 'vue'\n\nexport default {\n  setup() {\n    onActivated(() => {\n      console.log('activated!')\n    })\n    onDeactivated(() => {\n      console.log('deactivated!')\n    })\n  }\n}\n```\n\n## 使用例\n\n### 基本的な使用\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### include/exclude の使用\n\n```vue\n<template>\n  <!-- ComponentA と ComponentB のみキャッシュ -->\n  <KeepAlive include=\"ComponentA,ComponentB\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- ComponentC 以外をキャッシュ -->\n  <KeepAlive exclude=\"ComponentC\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- 正規表現でマッチ -->\n  <KeepAlive :include=\"/^Tab/\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### max の使用\n\n```vue\n<template>\n  <!-- 最大 10 コンポーネントまでキャッシュ（LRU） -->\n  <KeepAlive :max=\"10\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n## レンダラーとの連携\n\nKeepAlive はレンダラーと密接に連携して動作します．\n\n### mountComponent での KeepAlive 検出\n\n```ts\n// packages/runtime-core/src/renderer.ts\nconst mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {\n  const instance: ComponentInternalInstance = (\n    initialVNode.component = createComponentInstance(initialVNode, parentComponent)\n  );\n\n  // KeepAlive コンポーネントの場合、renderer を注入\n  if (isKeepAlive(initialVNode)) {\n    (instance as KeepAliveContext).renderer = {\n      p: patch,   // パッチ関数\n      m: move,    // DOM 移動関数\n      um: unmount, // アンマウント関数\n      o: options,  // ホストオプション（createElement 等）\n    };\n  }\n\n  // ... 通常のマウント処理\n};\n```\n\n### processComponent での KEPT_ALIVE 判定\n\n```ts\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null = null,\n) => {\n  if (n1 == null) {\n    // 新規マウント\n    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n      // キャッシュから復元：activate を呼び出す\n      (parentComponent as KeepAliveContext).activate(\n        n2,\n        container,\n        anchor,\n        parentComponent as ComponentInternalInstance\n      );\n    } else {\n      // 通常のマウント\n      mountComponent(n2, container, anchor, parentComponent);\n    }\n  } else {\n    updateComponent(n1, n2);\n  }\n};\n```\n\n### unmount での SHOULD_KEEP_ALIVE 判定\n\n```ts\nconst unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {\n  const { type, shapeFlag, children } = vnode;\n\n  // KeepAlive 管理下のコンポーネントは削除せず deactivate\n  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n    (parentComponent as KeepAliveContext).deactivate(vnode);\n    return;\n  }\n\n  // 通常のアンマウント処理\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    unmountComponent(vnode.component!);\n  }\n  // ...\n};\n```\n\n## 処理フロー\n\n```\n初回マウント:\nKeepAlive render\n  → slot から子を取得\n  → cache に存在しない → keys に追加\n  → COMPONENT_SHOULD_KEEP_ALIVE フラグ設定\n  → vnode を返却\n      ↓\nprocessComponent\n  → COMPONENT_KEPT_ALIVE なし → mountComponent\n  → isKeepAlive(vnode) → renderer を注入\n  → 通常のコンポーネントマウント\n\nキャッシュからの復元:\nKeepAlive render\n  → slot から子を取得\n  → cache にヒット → el/component を再利用\n  → COMPONENT_KEPT_ALIVE フラグ追加\n  → keys の順序を更新（LRU）\n  → vnode を返却\n      ↓\nprocessComponent\n  → COMPONENT_KEPT_ALIVE あり\n  → parentComponent.activate() 呼び出し\n      ↓\nactivate\n  → move で隠しコンテナから実コンテナへ移動\n  → patch で props の変更を適用\n  → instance.isDeactivated = false\n  → onActivated フック呼び出し\n\n非アクティブ化:\nunmount\n  → COMPONENT_SHOULD_KEEP_ALIVE あり\n  → parentComponent.deactivate() 呼び出し\n      ↓\ndeactivate\n  → move で隠しコンテナへ移動（DOM は削除されない）\n  → instance.isDeactivated = true\n  → onDeactivated フック呼び出し\n  → cache に保持されたまま\n```\n\n<KawaikoNote variant=\"warning\" title=\"メモリ使用量に注意！\">\n\nKeepAlive でキャッシュされたコンポーネントはメモリに残り続けます．\nキャッシュしすぎるとメモリを圧迫するので，`max` プロパティで上限を設定しましょう．\nLRU（最近使われていないものから削除）で自動的に管理されます！\n\n</KawaikoNote>\n\n## まとめ\n\nKeepAlive の実装は以下の要素で構成されています：\n\n1. **キャッシュシステム**: Map と Set を使用した LRU キャッシュ\n2. **隠しコンテナ**: 非アクティブな DOM を保持（`createElement(\"div\")`）\n3. **activate/deactivate**: DOM の移動とライフサイクル管理\n4. **ShapeFlags**: レンダラーとの連携\n   - `COMPONENT_SHOULD_KEEP_ALIVE`: unmount 時に deactivate を呼び出す\n   - `COMPONENT_KEPT_ALIVE`: mount 時に activate を呼び出す\n5. **renderer 注入**: KeepAlive は patch/move/unmount 関数への参照を保持\n6. **include/exclude/max**: 柔軟なキャッシュ制御\n\nKeepAlive はコンポーネントの状態を保持しながらパフォーマンスを向上させる強力な機能ですが，メモリ使用量とのトレードオフがあるため，適切な `max` 値の設定が重要です．\n\n<KawaikoNote variant=\"surprise\" title=\"KeepAlive 完成！\">\n\nコンポーネントを「消さずに隠す」というシンプルなアイデアですが，\nレンダラーとの連携や LRU キャッシュなど，実装はなかなか奥深いですね！\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/020_keep_alive)\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/030-builtins/020-suspense.md",
    "content": "---\nwip: true\n---\n\n# Coming Soon\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/030-builtins/030-transition.md",
    "content": "# Transition\n\n## Transition とは\n\n`<Transition>` は，要素やコンポーネントの表示・非表示の切り替え時にアニメーションを適用するための組み込みコンポーネントです．CSS トランジション/アニメーションと連携して，スムーズな UI 遷移を実現します．\n\n<KawaikoNote variant=\"question\" title=\"なぜ Transition が必要？\">\n\n要素の表示/非表示を `v-if` で切り替えると，瞬間的に消えたり現れたりします．\nTransition を使うと，フェードイン/アウトやスライドなどの\nアニメーションを簡単に追加できます！\n\n</KawaikoNote>\n\n主なユースケース：\n\n1. **v-if / v-show との組み合わせ**: 条件付きレンダリング時のアニメーション\n2. **動的コンポーネント**: `<component :is>` での切り替えアニメーション\n3. **ルート遷移**: ページ間のトランジション効果\n\n## 基本的な使い方\n\n```vue\n<template>\n  <button @click=\"show = !show\">Toggle</button>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n## 実装の概要\n\n### Props の定義\n\n```ts\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  appearFromClass?: string;\n  appearActiveClass?: string;\n  appearToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  appear?: boolean;\n  // ライフサイクルフック\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onEnterCancelled?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n  onLeaveCancelled?: (el: Element) => void;\n  // appear 用フック\n  onBeforeAppear?: (el: Element) => void;\n  onAppear?: (el: Element, done: () => void) => void;\n  onAfterAppear?: (el: Element) => void;\n  onAppearCancelled?: (el: Element) => void;\n}\n```\n\n### TransitionHooks インターフェース\n\n```ts\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n```\n\nレンダラーはこのインターフェースを通じて Transition と連携します．\n\n## CSS クラスのライフサイクル\n\nTransition は以下の CSS クラスを自動的に付与・削除します：\n\n### Enter（要素の表示）\n\n1. **v-enter-from**: 開始状態．要素が挿入される前に追加，1 フレーム後に削除\n2. **v-enter-active**: アクティブ状態．トランジション全体で適用\n3. **v-enter-to**: 終了状態．開始から 1 フレーム後に追加，トランジション終了時に削除\n\n### Leave（要素の非表示）\n\n1. **v-leave-from**: 開始状態．leave トランジション開始時に追加，1 フレーム後に削除\n2. **v-leave-active**: アクティブ状態．トランジション全体で適用\n3. **v-leave-to**: 終了状態．開始から 1 フレーム後に追加，トランジション終了時に削除\n\n```\nEnter:\n┌──────────────────────────────────────────┐\n│ v-enter-from → (1 frame) → v-enter-to   │\n│ ├─────── v-enter-active ──────────────┤ │\n└──────────────────────────────────────────┘\n\nLeave:\n┌──────────────────────────────────────────┐\n│ v-leave-from → (1 frame) → v-leave-to   │\n│ ├─────── v-leave-active ──────────────┤ │\n└──────────────────────────────────────────┘\n```\n\n## コアロジックの実装\n\n### resolveTransitionProps\n\nProps を解析し，TransitionHooks を生成します．\n\n```ts\nexport function resolveTransitionProps(\n  rawProps: TransitionProps\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    // ... 他のクラス\n    mode = \"default\",\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  // フック関数を生成\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      callHook(onBeforeEnter, [el]);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter: makeEnterHook(false),\n    leave(el, done) {\n      // leave ロジック\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n```\n\n### CSS クラスの管理\n\n```ts\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function addTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n```\n\n`_vtc`（Vue Transition Classes）プロパティで，現在適用されているトランジションクラスを追跡します．\n\n### nextFrame\n\nCSS トランジションを正しく動作させるため，2 フレーム待ってからクラスを変更します．\n\n```ts\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n```\n\n1 フレーム目でブラウザが初期状態を認識し，2 フレーム目で変更を適用することで，トランジションが確実に発火します．\n\n<KawaikoNote variant=\"funny\" title=\"2 フレーム待つ理由\">\n\n「なぜ 2 回も `requestAnimationFrame` を呼ぶの？」と思いますよね．\n1 回目でブラウザに「これが初期状態だよ」と教え，\n2 回目で「これが終了状態だよ」と教えることで，\nブラウザがトランジションを認識できるようになります！\n\n</KawaikoNote>\n\n### Enter フック\n\n```ts\nconst makeEnterHook = (isAppear: boolean) => {\n  return (el: Element, done: () => void) => {\n    const hook = isAppear ? onAppear : onEnter;\n    const resolve = () => finishEnter(el, isAppear, done);\n\n    callHook(hook, [el, resolve]);\n\n    nextFrame(() => {\n      removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass);\n      addTransitionClass(el, isAppear ? appearToClass : enterToClass);\n      if (!hasExplicitCallback(hook)) {\n        whenTransitionEnds(el, type, enterDuration, resolve);\n      }\n    });\n  };\n};\n```\n\n1. ユーザー定義フックを呼び出し\n2. 2 フレーム後に from クラスを削除，to クラスを追加\n3. トランジション終了を検知して完了処理\n\n### Leave フック\n\n```ts\nleave(el, done) {\n  const resolve = () => finishLeave(el, done);\n  addTransitionClass(el, leaveFromClass);\n  // 強制リフロー\n  forceReflow();\n  addTransitionClass(el, leaveActiveClass);\n\n  nextFrame(() => {\n    removeTransitionClass(el, leaveFromClass);\n    addTransitionClass(el, leaveToClass);\n    if (!hasExplicitCallback(onLeave)) {\n      whenTransitionEnds(el, type, leaveDuration, resolve);\n    }\n  });\n  callHook(onLeave, [el, resolve]);\n}\n```\n\n## トランジション終了の検知\n\n### getTransitionInfo\n\nCSS から transition/animation の情報を取得します．\n\n```ts\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"]\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  // transition と animation のどちらを使用するか決定\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    // animation の場合\n  } else {\n    // 自動検出\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0\n      ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION)\n      : null;\n  }\n\n  return { type, timeout, propCount, hasTransform };\n}\n```\n\n### whenTransitionEnds\n\nトランジション終了時にコールバックを実行します．\n\n```ts\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  // 明示的なタイムアウトがあればそれを使用\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout);\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\"; // \"transitionend\" or \"animationend\"\n  let ended = 0;\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  // タイムアウトのフォールバック\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n```\n\nポイント：\n- `transitionend` / `animationend` イベントを監視\n- プロパティの数だけイベントを待つ\n- タイムアウトによるフォールバック（イベントが発火しない場合の保険）\n- `_endId` で古いトランジションをキャンセル\n\n### forceReflow\n\nCSS トランジションを確実に発火させるため，強制的にリフローを発生させます．\n\n```ts\nexport function forceReflow(): number {\n  return document.body.offsetHeight;\n}\n```\n\n`offsetHeight` を読み取ることで，ブラウザにスタイルの再計算を強制します．\n\n<KawaikoNote variant=\"warning\" title=\"なぜリフローを強制する？\">\n\nCSS クラスを連続で追加しても，ブラウザは最適化のために\nスタイルの再計算をまとめて行うことがあります．\n`offsetHeight` を読むことで「今すぐ計算して！」と強制できます．\n\n</KawaikoNote>\n\n## Transition コンポーネント本体\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // VNode に transition フックを設定\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\nTransition 自体は DOM 要素をレンダリングせず，子の VNode に `transition` プロパティを付与するだけです．レンダラーはこのプロパティを見てフックを呼び出します．\n\n## 使用例\n\n### 基本的なフェード\n\n```vue\n<template>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n### スライドアニメーション\n\n```vue\n<template>\n  <Transition name=\"slide\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.slide-enter-active,\n.slide-leave-active {\n  transition: all 0.3s ease;\n}\n.slide-enter-from {\n  transform: translateX(-100%);\n  opacity: 0;\n}\n.slide-leave-to {\n  transform: translateX(100%);\n  opacity: 0;\n}\n</style>\n```\n\n### JavaScript フック\n\n```vue\n<template>\n  <Transition\n    @before-enter=\"onBeforeEnter\"\n    @enter=\"onEnter\"\n    @after-enter=\"onAfterEnter\"\n    @leave=\"onLeave\"\n    :css=\"false\"\n  >\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<script setup>\nfunction onBeforeEnter(el) {\n  el.style.opacity = 0;\n}\n\nfunction onEnter(el, done) {\n  // GSAP などのアニメーションライブラリを使用\n  gsap.to(el, {\n    opacity: 1,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n\nfunction onLeave(el, done) {\n  gsap.to(el, {\n    opacity: 0,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n</script>\n```\n\n### 明示的な duration\n\n```vue\n<template>\n  <!-- enter: 300ms, leave: 500ms -->\n  <Transition name=\"fade\" :duration=\"{ enter: 300, leave: 500 }\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n```\n\n## VNode との連携\n\n### VNode.transition プロパティ\n\nVNode には `transition` プロパティがあり，ここに TransitionHooks が格納されます．\n\n```ts\n// packages/runtime-core/src/vnode.ts\nexport interface VNode<HostNode = any> {\n  // ... 他のプロパティ\n\n  // transition\n  transition: any | null;\n}\n```\n\n### Transition コンポーネントでの設定\n\nTransition コンポーネントは，子の VNode に `transition` プロパティを設定します．\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // VNode に transition フックを設定\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\n### レンダラーでの処理\n\nレンダラーは VNode の `transition` プロパティを検出し，適切なタイミングでフックを呼び出します：\n\n1. **要素の挿入時**: `beforeEnter` → DOM 挿入 → `enter`\n2. **要素の削除時**: `leave` → DOM 削除\n\n```ts\n// 概念的な処理フロー\nconst mountElement = (vnode, container, anchor) => {\n  const el = createElement(vnode.type);\n\n  // transition がある場合は beforeEnter を呼び出す\n  if (vnode.transition) {\n    vnode.transition.beforeEnter(el);\n  }\n\n  // DOM に挿入\n  insert(el, container, anchor);\n\n  // transition がある場合は enter を呼び出す\n  if (vnode.transition) {\n    vnode.transition.enter(el);\n  }\n};\n\nconst unmountElement = (vnode) => {\n  const el = vnode.el;\n\n  // transition がある場合は leave を呼び出す\n  if (vnode.transition) {\n    vnode.transition.leave(el, () => {\n      // leave 完了後に DOM から削除\n      remove(el);\n    });\n  } else {\n    remove(el);\n  }\n};\n```\n\n## 処理フロー\n\n```\nTransition コンポーネント render\n  ↓\nresolveTransitionProps で TransitionHooks 生成\n  ↓\nchild.transition = innerProps\n  ↓\nレンダラー mountElement\n  ├── beforeEnter(el)\n  │   └── enterFromClass/enterActiveClass を追加\n  ├── insert(el, container)\n  └── enter(el, done)\n      └── nextFrame で\n          ├── enterFromClass を削除\n          ├── enterToClass を追加\n          └── whenTransitionEnds で完了待ち\n              └── done() で finishEnter\n\nレンダラー unmountElement\n  └── transition.leave(el, remove)\n      ├── leaveFromClass 追加\n      ├── forceReflow()\n      ├── leaveActiveClass 追加\n      └── nextFrame で\n          ├── leaveFromClass を削除\n          ├── leaveToClass を追加\n          └── whenTransitionEnds で完了待ち\n              └── remove() で DOM 削除\n```\n\n## まとめ\n\nTransition の実装は以下の要素で構成されています：\n\n1. **CSS クラス管理**: enter/leave の各フェーズでクラスを付与・削除\n2. **nextFrame**: 2 フレーム待機でトランジション発火を保証\n3. **forceReflow**: 強制リフローでスタイル再計算\n4. **whenTransitionEnds**: transitionend/animationend の監視\n5. **JavaScript フック**: CSS を使わないアニメーションのサポート\n6. **VNode.transition**: レンダラーがフックを呼び出すためのプロパティ\n\nTransition は CSS トランジション/アニメーションと密接に連携し，ブラウザの描画パイプラインを理解した上で実装されています．\n\n<KawaikoNote variant=\"surprise\" title=\"Transition 完成！\">\n\nCSS のクラス操作だけでなく，フレームのタイミングやリフローの制御など，\nブラウザの仕組みを深く理解した実装になっています．\n意外と奥が深いですね！\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/030_transition)\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/040-optimizations/010-static-hoisting.md",
    "content": "# Static Hoisting（静的巻き上げ）\n\n## Static Hoisting とは\n\nStatic Hoisting は，テンプレートコンパイル時の最適化テクニックの一つです．テンプレート内の静的な（リアクティブな依存関係を持たない）ノードを検出し，レンダー関数の外部に「巻き上げ」（hoist）することで，再レンダリング時のパフォーマンスを向上させます．\n\n<KawaikoNote variant=\"question\" title=\"なぜ巻き上げ（hoist）と呼ぶ？\">\n\nJavaScript の「変数の巻き上げ（hoisting）」と同じ発想です．\nレンダー関数の中にある静的なコードを，関数の外に「持ち上げる」ことで，\n関数が呼ばれるたびに再生成する必要がなくなります！\n\n</KawaikoNote>\n\n### 最適化の効果\n\n1. **VNode 生成のスキップ**: 静的なノードは初回のみ生成され，再利用される\n2. **メモリ使用量の削減**: 同一の VNode オブジェクトを再利用\n3. **パッチ処理のスキップ**: 静的ノードは比較対象から除外可能\n\n## 最適化前後の比較\n\n### テンプレート\n\n```vue\n<template>\n  <div>\n    <h1>Hello World</h1>\n    <p>{{ message }}</p>\n  </div>\n</template>\n```\n\n### 最適化なしのコンパイル結果\n\n```js\nfunction render() {\n  return h('div', null, [\n    h('h1', null, 'Hello World'),  // 毎回生成\n    h('p', null, message.value)\n  ])\n}\n```\n\n### Static Hoisting 適用後\n\n```js\nconst _hoisted_1 = h('h1', null, 'Hello World')  // 外部で一度だけ生成\n\nfunction render() {\n  return h('div', null, [\n    _hoisted_1,  // 参照を再利用\n    h('p', null, message.value)\n  ])\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"劇的ビフォーアフター！\">\n\n毎回 VNode を生成していたのが，一度生成した VNode を使い回すだけに．\nヘッダーやフッターなど，変わらない部分が多いほど効果絶大です！\n\n</KawaikoNote>\n\n## 実装の概要\n\n### ConstantTypes\n\nノードの静的性を表す列挙型です．\n\n```ts\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,    // 動的（巻き上げ不可）\n  CAN_SKIP_PATCH = 1,  // パッチ処理をスキップ可能\n  CAN_HOIST = 2,       // 巻き上げ可能\n  CAN_STRINGIFY = 3,   // 文字列化可能（さらに最適化可能）\n}\n```\n\n### hoistStatic 関数\n\n変換フェーズの後で呼び出され，静的ノードを検出して巻き上げます．\n\n```ts\nexport function hoistStatic(root: RootNode, context: TransformContext): void {\n  walk(root, context, new Map());\n}\n```\n\n### walk 関数\n\nAST を再帰的に走査し，巻き上げ可能なノードを検出します．\n\n```ts\nfunction walk(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): void {\n  const { children } = node as RootNode | ElementNode;\n  if (!children) return;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (\n      child.type === NodeTypes.ELEMENT &&\n      child.tagType === 0 // プレーン要素のみ対象\n    ) {\n      const constantType = getConstantType(child, context, resultCache);\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          // 巻き上げ可能\n          const codegenNode = child.codegenNode as VNodeCall | undefined;\n          if (codegenNode && codegenNode.type === NodeTypes.VNODE_CALL) {\n            codegenNode.isStatic = true;\n            context.hoists.push(codegenNode);\n            // codegenNode を巻き上げ参照に置き換え\n            child.codegenNode = context.hoist(codegenNode) as VNodeCall;\n          }\n        }\n      } else {\n        // 動的な場合は子を再帰的にチェック\n        walk(child, context, resultCache);\n      }\n    }\n  }\n}\n```\n\nポイント：\n1. プレーン要素（コンポーネントではない）のみを対象\n2. 静的なノードは `context.hoists` に追加\n3. 元の `codegenNode` を `_hoisted_N` への参照に置き換え\n4. 動的なノードは子ノードを再帰的にチェック\n\n### getConstantType 関数\n\nノードが静的かどうかを判定します．\n\n```ts\nexport function getConstantType(\n  node: TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): ConstantTypes {\n  // キャッシュをチェック\n  const cached = resultCache.get(node);\n  if (cached !== undefined) {\n    return cached;\n  }\n\n  if (node.type === NodeTypes.ELEMENT) {\n    // コンポーネントは巻き上げ不可\n    if (node.tagType !== 0) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    const element = node as PlainElementNode;\n    const codegenNode = element.codegenNode;\n\n    if (!codegenNode || codegenNode.type !== NodeTypes.VNODE_CALL) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    // 動的な props がないかチェック\n    if (codegenNode.props) {\n      const propsType = codegenNode.props.type;\n      if (propsType !== NodeTypes.JS_OBJECT_EXPRESSION) {\n        resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n\n      const properties = codegenNode.props.properties;\n      for (let i = 0; i < properties.length; i++) {\n        const { key, value } = properties[i];\n        // キーと値が両方静的でなければ不可\n        if (key.type !== NodeTypes.SIMPLE_EXPRESSION || !key.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n        if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // 子要素も再帰的にチェック\n    if (element.children) {\n      for (let i = 0; i < element.children.length; i++) {\n        const child = element.children[i];\n        const childType = getConstantType(child, context, resultCache);\n        if (childType === ConstantTypes.NOT_CONSTANT) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // ディレクティブがあれば不可\n    if (element.props && element.props.length > 0) {\n      for (const prop of element.props) {\n        if (prop.type === NodeTypes.DIRECTIVE) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    resultCache.set(node, ConstantTypes.CAN_HOIST);\n    return ConstantTypes.CAN_HOIST;\n  }\n\n  // テキストノードは巻き上げ可能\n  if (node.type === NodeTypes.TEXT) {\n    resultCache.set(node, ConstantTypes.CAN_STRINGIFY);\n    return ConstantTypes.CAN_STRINGIFY;\n  }\n\n  // 補間（{{ }}）は動的\n  if (node.type === NodeTypes.INTERPOLATION) {\n    resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n    return ConstantTypes.NOT_CONSTANT;\n  }\n\n  resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n  return ConstantTypes.NOT_CONSTANT;\n}\n```\n\n判定ロジック：\n1. **コンポーネント**: 常に動的（props や slots が変わる可能性）\n2. **動的 props**: バインディング（`:class`，`:style` など）があれば動的\n3. **ディレクティブ**: `v-if`，`v-for` などがあれば動的\n4. **補間式**: `{{ message }}` は動的\n5. **子要素**: 一つでも動的な子があれば親も動的\n6. **静的テキスト/属性**: 巻き上げ可能\n\n### コード生成\n\n```ts\nfunction genHoists(\n  hoists: (TemplateChildNode | ExpressionNode)[],\n  context: CodegenContext\n) {\n  const { push, newline } = context;\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context);\n      newline();\n    }\n  }\n}\n```\n\n`hoists` 配列に蓄積されたノードを，レンダー関数の前に定数として生成します．\n\n### TransformContext の hoist メソッド\n\n```ts\nhoist(exp) {\n  context.hoists.push(exp);\n  const identifier = createSimpleExpression(\n    `_hoisted_${context.hoists.length}`,\n    false,\n  );\n  return identifier;\n}\n```\n\n元のノードを `hoists` 配列に追加し，`_hoisted_N` という識別子を返します．これがレンダー関数内で参照されます．\n\n## 巻き上げ可能な例\n\n```vue\n<template>\n  <!-- ✅ 巻き上げ可能 -->\n  <div class=\"static\">Static content</div>\n  <img src=\"/logo.png\" alt=\"Logo\">\n  <p>Fixed text</p>\n\n  <!-- ❌ 巻き上げ不可 -->\n  <div :class=\"dynamicClass\">Dynamic</div>\n  <p>{{ message }}</p>\n  <div v-if=\"show\">Conditional</div>\n  <MyComponent />\n</template>\n```\n\n## transform フェーズでの呼び出し\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions): void {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n\n  // hoistStatic オプションが有効な場合に実行\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n\n  createRootCodegen(root, context);\n  root.components = [...context.components];\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.hoists = context.hoists;\n}\n```\n\n## オプション\n\n```ts\nexport interface TransformOptions {\n  hoistStatic?: boolean;  // 静的巻き上げを有効化\n  // ...\n}\n```\n\n## 生成されるコード例\n\n入力テンプレート：\n```vue\n<template>\n  <div>\n    <header>\n      <h1>My App</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ content }}</p>\n    </main>\n  </div>\n</template>\n```\n\n生成コード：\n```js\nimport { createVNode as _createVNode, toDisplayString as _toDisplayString } from 'vue'\n\n// 静的ノードは外部に巻き上げ\nconst _hoisted_1 = _createVNode(\"header\", null, [\n  _createVNode(\"h1\", null, \"My App\"),\n  _createVNode(\"nav\", null, [\n    _createVNode(\"a\", { href: \"/home\" }, \"Home\"),\n    _createVNode(\"a\", { href: \"/about\" }, \"About\")\n  ])\n])\n\nfunction render(_ctx) {\n  return _createVNode(\"div\", null, [\n    _hoisted_1,  // 参照を再利用\n    _createVNode(\"main\", null, [\n      _createVNode(\"p\", null, _toDisplayString(_ctx.content))  // 動的部分\n    ])\n  ])\n}\n```\n\n## まとめ\n\nStatic Hoisting の実装は以下の要素で構成されています：\n\n1. **ConstantTypes**: ノードの静的性レベルを表す列挙型\n2. **getConstantType**: ノードが静的かどうかを判定\n3. **walk**: AST を走査して巻き上げ可能なノードを検出\n4. **hoist**: ノードを巻き上げ配列に追加し参照を返す\n5. **genHoists**: 巻き上げられたノードをコード生成\n\nこの最適化により，大きなテンプレートで多くの静的コンテンツがある場合に，再レンダリングのパフォーマンスが大幅に向上します．特に，ヘッダー，フッター，サイドバーなど変更されない UI 部分で効果的です．\n\n<KawaikoNote variant=\"surprise\" title=\"Static Hoisting 完成！\">\n\nコンパイラが「この部分は変わらないな」と判断して自動的に最適化してくれます．\nテンプレートベースのフレームワークならではの強みですね！\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/040_static_hoisting)\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/040-optimizations/020-patch-flags.md",
    "content": "# Patch Flags\n\n## Patch Flags とは\n\nPatch Flags は，コンパイラが生成する最適化ヒントです．VNode にフラグを付与することで，ランタイムの差分検出（diffing）アルゴリズムが不要なチェックをスキップし，パフォーマンスを向上させます．\n\n<KawaikoNote variant=\"question\" title=\"なぜコンパイラが最適化？\">\n\nテンプレートを書く人間は「ここは動的」「ここは静的」と把握していますが，\n従来の Virtual DOM はそれを知りません．コンパイラがその情報をランタイムに伝えることで，\n無駄な比較を省けるようになります！\n\n</KawaikoNote>\n\n### 最適化の仕組み\n\n通常の Virtual DOM の差分検出では，すべてのプロパティと子要素を比較する必要があります．しかし，コンパイラはテンプレートを解析する段階で「どの部分が動的か」を知っています．この情報を Patch Flags として VNode に埋め込むことで，ランタイムは変更される可能性のある部分だけをチェックできます．\n\n## PatchFlags の定義\n\n```ts\nexport const enum PatchFlags {\n  /**\n   * 動的な textContent を持つ要素\n   */\n  TEXT = 1,\n\n  /**\n   * 動的な class バインディングを持つ要素\n   */\n  CLASS = 1 << 1,  // 2\n\n  /**\n   * 動的な style を持つ要素\n   */\n  STYLE = 1 << 2,  // 4\n\n  /**\n   * class/style 以外の動的な props を持つ要素\n   */\n  PROPS = 1 << 3,  // 8\n\n  /**\n   * 動的なキーを持つ props がある要素\n   */\n  FULL_PROPS = 1 << 4,  // 16\n\n  /**\n   * hydration 時に props の処理が必要\n   */\n  NEED_HYDRATION = 1 << 5,  // 32\n\n  /**\n   * 子の順序が変わらない Fragment\n   */\n  STABLE_FRAGMENT = 1 << 6,  // 64\n\n  /**\n   * keyed な子を持つ Fragment\n   */\n  KEYED_FRAGMENT = 1 << 7,  // 128\n\n  /**\n   * keyed でない子を持つ Fragment\n   */\n  UNKEYED_FRAGMENT = 1 << 8,  // 256\n\n  /**\n   * props 以外のパッチが必要（ref、ディレクティブなど）\n   */\n  NEED_PATCH = 1 << 9,  // 512\n\n  /**\n   * 動的なスロットを持つコンポーネント\n   */\n  DYNAMIC_SLOTS = 1 << 10,  // 1024\n\n  /**\n   * 開発用：ルートにコメントがある Fragment\n   */\n  DEV_ROOT_FRAGMENT = 1 << 11,  // 2048\n\n  // 特殊フラグ（負の整数）\n\n  /**\n   * キャッシュされた静的 VNode\n   */\n  CACHED = -1,\n\n  /**\n   * 最適化モードを終了するヒント\n   */\n  BAIL = -2,\n}\n```\n\n## ビット演算による組み合わせ\n\nPatch Flags はビットフラグとして設計されており，複数のフラグを組み合わせることができます．\n\n```ts\n// フラグの組み合わせ\nconst flag = PatchFlags.TEXT | PatchFlags.CLASS;  // 3 (0b11)\n\n// フラグのチェック\nif (flag & PatchFlags.TEXT) {\n  // TEXT フラグが設定されている\n}\n\nif (flag & PatchFlags.CLASS) {\n  // CLASS フラグが設定されている\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"ビット演算の魔法\">\n\n`1 << 1` は `2`，`1 << 2` は `4`...ビットをずらすだけで独立したフラグが作れます．\n`|`（OR）で組み合わせ，`&`（AND）でチェック．シンプルだけど超効率的！\n\n</KawaikoNote>\n\n## テンプレートからの生成例\n\n### 動的テキスト\n\n```vue\n<template>\n  <p>{{ message }}</p>\n</template>\n```\n\n生成コード：\n```js\n// patchFlag = 1 (TEXT)\ncreateVNode(\"p\", null, toDisplayString(message), 1 /* TEXT */)\n```\n\n### 動的クラス\n\n```vue\n<template>\n  <div :class=\"dynamicClass\">Content</div>\n</template>\n```\n\n生成コード：\n```js\n// patchFlag = 2 (CLASS)\ncreateVNode(\"div\", { class: dynamicClass }, \"Content\", 2 /* CLASS */)\n```\n\n### 複数の動的プロパティ\n\n```vue\n<template>\n  <div :class=\"cls\" :style=\"styles\">{{ text }}</div>\n</template>\n```\n\n生成コード：\n```js\n// patchFlag = 7 (TEXT | CLASS | STYLE)\ncreateVNode(\"div\",\n  { class: cls, style: styles },\n  toDisplayString(text),\n  7 /* TEXT, CLASS, STYLE */\n)\n```\n\n### 動的 props\n\n```vue\n<template>\n  <input :value=\"inputValue\" :disabled=\"isDisabled\">\n</template>\n```\n\n生成コード：\n```js\n// patchFlag = 8 (PROPS)\n// dynamicProps で変更される可能性のある props を明示\ncreateVNode(\"input\",\n  { value: inputValue, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]\n)\n```\n\n## ランタイムでの活用\n\n### patchElement での最適化\n\n```ts\nfunction patchElement(n1: VNode, n2: VNode) {\n  const el = n2.el = n1.el;\n  const { patchFlag, dynamicProps } = n2;\n\n  if (patchFlag > 0) {\n    // 最適化パス：フラグに基づいて必要な部分だけ更新\n\n    if (patchFlag & PatchFlags.CLASS) {\n      // class のみ更新\n      if (n1.props?.class !== n2.props?.class) {\n        hostSetClass(el, n2.props?.class);\n      }\n    }\n\n    if (patchFlag & PatchFlags.STYLE) {\n      // style のみ更新\n      hostPatchStyle(el, n1.props?.style, n2.props?.style);\n    }\n\n    if (patchFlag & PatchFlags.PROPS) {\n      // 指定された props のみ更新\n      for (const key of dynamicProps!) {\n        const prev = n1.props?.[key];\n        const next = n2.props?.[key];\n        if (prev !== next) {\n          hostPatchProp(el, key, prev, next);\n        }\n      }\n    }\n\n    if (patchFlag & PatchFlags.TEXT) {\n      // テキストコンテンツのみ更新\n      if (n1.children !== n2.children) {\n        hostSetElementText(el, n2.children as string);\n      }\n    }\n  } else if (patchFlag === PatchFlags.FULL_PROPS) {\n    // すべての props をチェック\n    patchProps(el, n1.props, n2.props);\n  } else {\n    // フラグなし：フルの diff\n    patchProps(el, n1.props, n2.props);\n    patchChildren(n1, n2, el);\n  }\n}\n```\n\n### Fragment の最適化\n\n```ts\nfunction patchFragment(n1: VNode, n2: VNode) {\n  const { patchFlag } = n2;\n\n  if (patchFlag & PatchFlags.STABLE_FRAGMENT) {\n    // 子の順序が変わらない：シンプルな更新\n    patchBlockChildren(n1.children, n2.children);\n  } else if (patchFlag & PatchFlags.KEYED_FRAGMENT) {\n    // keyed children：キーベースの diff\n    patchKeyedChildren(n1.children, n2.children);\n  } else {\n    // unkeyed：フルの diff\n    patchUnkeyedChildren(n1.children, n2.children);\n  }\n}\n```\n\n## 特殊フラグ\n\n### CACHED (-1)\n\n静的な VNode がキャッシュされていることを示します．\n\n```js\nconst _hoisted_1 = createVNode(\"div\", null, \"Static\", -1 /* CACHED */);\n```\n\nキャッシュされた VNode は差分検出をスキップできます．\n\n### BAIL (-2)\n\n最適化モードを終了するヒントです．ユーザーが手書きの render 関数を使用している場合など，コンパイラの最適化が適用できない場合に使用されます．\n\n## dynamicProps\n\n`patchFlag` と一緒に使用される `dynamicProps` 配列は，どの props が動的かを明示します．\n\n```ts\n// 動的な props が value と disabled\ncreateVNode(\"input\",\n  { type: \"text\", value: val, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]  // dynamicProps\n)\n```\n\nこれにより，`type` は静的なので比較をスキップし，`value` と `disabled` のみをチェックできます．\n\n## Block Tree との連携\n\nPatch Flags は Block Tree 最適化と連携して動作します．Block は `dynamicChildren` 配列を持ち，動的な子ノードのみを追跡します．\n\n```ts\nconst block = openBlock();\nconst vnode = createBlock(\"div\", null, [\n  createVNode(\"p\", null, \"static\"),  // dynamicChildren に含まれない\n  createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // 含まれる\n]);\n// block.dynamicChildren = [動的な p のみ]\n```\n\nBlock の更新時は `dynamicChildren` のみを走査すれば良いため，静的な子ノードの比較をスキップできます．\n\n## 最適化の効果\n\n### Before（フラグなし）\n```\nすべての props を比較: O(n)\nすべての子を比較: O(m)\n合計: O(n + m)\n```\n\n### After（フラグあり）\n```\n動的な props のみ比較: O(k) where k << n\n動的な子のみ比較: O(l) where l << m\n合計: O(k + l)\n```\n\nテンプレートの大部分が静的な場合，この最適化は大きな効果を発揮します．\n\n## まとめ\n\nPatch Flags の実装は以下の要素で構成されています：\n\n1. **ビットフラグ**: 複数の動的要素を効率的に表現\n2. **コンパイラ統合**: テンプレート解析時に自動生成\n3. **ランタイム最適化**: フラグに基づいて不要な比較をスキップ\n4. **dynamicProps**: 動的な props を明示的に追跡\n5. **Block Tree 連携**: 動的な子ノードのみを効率的に更新\n\nPatch Flags は Vue 3 の Virtual DOM パフォーマンスを大幅に向上させる重要な最適化技術です．コンパイラとランタイムが協調することで，テンプレートベースのフレームワークの利点を最大限に活かしています．\n\n<KawaikoNote variant=\"surprise\" title=\"Patch Flags 完成！\">\n\n「テンプレートを解析できるなら，最適化のヒントも出せるよね」という発想から生まれた技術です．\nJSX にはない，テンプレートコンパイラならではの強みをぜひ体感してください！\n\n</KawaikoNote>\n\nここまでのソースコード:\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/050_patch_flags)\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/040-optimizations/030-tree-flattening.md",
    "content": "# Tree Flattening（Block Tree）\n\n## Tree Flattening とは\n\nTree Flattening（Block Tree）は，Vue 3 で導入された高度な最適化テクニックです．テンプレート内の動的なノードを「フラット化」して収集し，更新時にツリー全体を走査する代わりに，動的なノードだけを直接更新します．\n\n<KawaikoNote variant=\"question\" title=\"なぜ「平坦化」？\">\n\n従来の Virtual DOM は，更新時にツリー全体を再帰的に走査する必要がありました．\nTree Flattening は，動的なノードだけを配列に「平坦化」して収集することで，\nネストされた構造を無視して直接アクセスできるようにします．\n\n</KawaikoNote>\n\n## 従来の diff アルゴリズムの問題\n\n### テンプレート例\n\n```vue\n<template>\n  <div>\n    <header>\n      <h1>Static Title</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ dynamicText }}</p>  <!-- 唯一の動的部分 -->\n    </main>\n    <footer>\n      <p>Copyright 2024</p>\n    </footer>\n  </div>\n</template>\n```\n\n### 従来のアプローチ\n\n```\nツリー全体を走査:\ndiv\n├── header (静的)\n│   ├── h1 (静的)\n│   └── nav (静的)\n│       ├── a (静的)\n│       └── a (静的)\n├── main\n│   └── p (動的) ← 実際に更新が必要なのはここだけ\n└── footer (静的)\n    └── p (静的)\n\n→ 9つのノードを走査して，1つを更新\n```\n\n### Tree Flattening のアプローチ\n\n```\n動的ノードのみを収集:\ndynamicChildren = [p]\n\n→ 1つのノードを直接更新\n```\n\n<KawaikoNote variant=\"funny\" title=\"劇的な効率化！\">\n\n1000 個のノードのうち 10 個だけが動的な場合，\n従来は 1000 回の比較が必要ですが，\nTree Flattening なら 10 回の比較で済みます．\n\n</KawaikoNote>\n\n## Block の概念\n\n### Block とは\n\nBlock は「安定した構造を持つ VNode のサブツリー」です．Block 内では以下が保証されます：\n\n1. 子ノードの数が変わらない\n2. 子ノードの順序が変わらない\n3. 構造的ディレクティブ（`v-if`, `v-for`）がない\n\n### Block を作成する要素\n\n以下の要素は新しい Block を作成します：\n\n- ルート要素\n- `v-if` の各分岐\n- `v-for` の各アイテム\n- コンポーネント\n\n```vue\n<template>\n  <!-- Block 1: ルート -->\n  <div>\n    <p>{{ text1 }}</p>\n\n    <!-- Block 2: v-if -->\n    <div v-if=\"show\">\n      <p>{{ text2 }}</p>\n    </div>\n\n    <!-- Block 3, 4, ...: v-for の各アイテム -->\n    <div v-for=\"item in items\" :key=\"item.id\">\n      <p>{{ item.text }}</p>\n    </div>\n  </div>\n</template>\n```\n\n## VNode の拡張\n\n### dynamicChildren\n\nVNode に `dynamicChildren` プロパティを追加して，動的な子ノードを収集します．\n\n```ts\nexport interface VNode {\n  // ... 既存のプロパティ\n\n  /**\n   * Block 内の動的な子ノードのリスト\n   * 更新時にこれだけを走査すれば良い\n   */\n  dynamicChildren: VNode[] | null;\n\n  /**\n   * パッチ処理の最適化ヒント\n   */\n  patchFlag: number;\n\n  /**\n   * 動的なプロパティの名前リスト\n   */\n  dynamicProps: string[] | null;\n}\n```\n\n## openBlock と createBlock\n\n### Block のトラッキング\n\nBlock の作成は `openBlock` と `createBlock` のペアで行います．\n\n```ts\n// 現在トラッキング中の Block\nlet currentBlock: VNode[] | null = null;\n\nexport function openBlock(): void {\n  currentBlock = [];\n}\n\nexport function createBlock(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode = createVNode(type, props, children, patchFlag, dynamicProps);\n\n  // 収集した動的ノードを設定\n  vnode.dynamicChildren = currentBlock;\n  currentBlock = null;\n\n  return vnode;\n}\n```\n\n### 動的ノードの収集\n\n`createVNode` 内で，patchFlag がある VNode を currentBlock に追加します．\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children,\n    patchFlag: patchFlag || 0,\n    dynamicProps: dynamicProps || null,\n    dynamicChildren: null,\n    // ...\n  };\n\n  // patchFlag がある = 動的なノード\n  // currentBlock があれば追加\n  if (patchFlag !== undefined && patchFlag > 0 && currentBlock) {\n    currentBlock.push(vnode);\n  }\n\n  return vnode;\n}\n```\n\n## 生成されるコード\n\n### テンプレート\n\n```vue\n<template>\n  <div>\n    <h1>Static Title</h1>\n    <p>{{ message }}</p>\n    <span :class=\"cls\">{{ text }}</span>\n  </div>\n</template>\n```\n\n### 生成されるレンダリング関数\n\n```js\nimport { openBlock, createBlock, createVNode, toDisplayString } from 'vue'\n\n// 静的ノードは外部に巻き上げ\nconst _hoisted_1 = createVNode(\"h1\", null, \"Static Title\")\n\nfunction render(_ctx) {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // 静的（dynamicChildren に含まれない）\n      createVNode(\"p\", null, toDisplayString(_ctx.message), 1 /* TEXT */),\n      createVNode(\"span\", { class: _ctx.cls }, toDisplayString(_ctx.text), 3 /* TEXT | CLASS */)\n    ])\n  )\n}\n\n// 結果の VNode:\n// {\n//   type: \"div\",\n//   children: [_hoisted_1, p, span],\n//   dynamicChildren: [p, span]  // 動的ノードのみ\n// }\n```\n\n## patchBlockChildren の実装\n\nBlock の更新時は `dynamicChildren` のみを走査します．\n\n```ts\nfunction patchBlockChildren(\n  oldChildren: VNode[],\n  newChildren: VNode[],\n  container: RendererElement,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  for (let i = 0; i < newChildren.length; i++) {\n    const oldVNode = oldChildren[i];\n    const newVNode = newChildren[i];\n\n    // 動的ノードのみをパッチ\n    patch(oldVNode, newVNode, container, null, parentComponent);\n  }\n}\n```\n\n### patchElement での使用\n\n```ts\nfunction patchElement(\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  const el = (n2.el = n1.el!);\n  const oldProps = n1.props || {};\n  const newProps = n2.props || {};\n\n  // patchFlag を使った最適化されたパッチ\n  const { patchFlag, dynamicChildren } = n2;\n\n  if (patchFlag > 0) {\n    // patchFlag に基づいて特定のプロパティのみ更新\n    if (patchFlag & PatchFlags.CLASS) {\n      patchClass(el, newProps.class);\n    }\n    if (patchFlag & PatchFlags.STYLE) {\n      patchStyle(el, oldProps.style, newProps.style);\n    }\n    if (patchFlag & PatchFlags.TEXT) {\n      if (n1.children !== n2.children) {\n        el.textContent = n2.children as string;\n      }\n    }\n    // ...\n  }\n\n  // dynamicChildren がある場合は最適化パス\n  if (dynamicChildren) {\n    patchBlockChildren(\n      n1.dynamicChildren!,\n      dynamicChildren,\n      el,\n      parentComponent\n    );\n  } else {\n    // フォールバック: 通常の子要素パッチ\n    patchChildren(n1, n2, el, parentComponent);\n  }\n}\n```\n\n## 最適化の効果\n\n1000 個のリストアイテムのうち，1 つだけを更新するケースを考えると：\n\n- **フル diff**: 1000 個以上のノードを走査\n- **Patch Flags のみ**: 1000 個のノードを走査（プロパティ比較は最適化）\n- **Tree Flattening**: 動的なノード（1 個）のみを走査\n\nこのように，動的ノードが少ないほど Tree Flattening の効果は大きくなります．\n\n## Block が壊れるケース\n\n以下のケースでは Block 最適化が無効になります（BAIL モード）：\n\n1. **構造的ディレクティブ**: `v-if`, `v-for` は新しい Block を作成\n2. **動的コンポーネント**: `<component :is=\"...\">`\n3. **スロットの出口**: `<slot />`\n\n```vue\n<template>\n  <div>\n    <!-- ここで Block が分割される -->\n    <div v-if=\"show\">\n      <p>{{ a }}</p>  <!-- Block A の dynamicChildren -->\n    </div>\n    <div v-else>\n      <p>{{ b }}</p>  <!-- Block B の dynamicChildren -->\n    </div>\n  </div>\n</template>\n```\n\n## Static Hoisting との連携\n\nTree Flattening は Static Hoisting と組み合わせることで最大の効果を発揮します．\n\n```ts\n// 静的ノードは巻き上げられ，dynamicChildren に含まれない\nconst _hoisted_1 = createVNode(\"header\", null, [\n  createVNode(\"h1\", null, \"Title\"),\n  createVNode(\"nav\", null, [/* ... */])\n]);\n\nfunction render() {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // 静的: スキップ\n      createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // 動的: 追跡\n    ])\n  )\n}\n```\n\n1. **Static Hoisting**: 静的ノードを関数外に巻き上げ（VNode 生成をスキップ）\n2. **Tree Flattening**: 動的ノードのみを収集（diff 対象を限定）\n3. **Patch Flags**: 動的プロパティのみを更新（プロパティ比較を最適化）\n\n## 処理フロー\n\n```\n[コンパイル時]\nテンプレート解析\n  ↓\n静的ノードの検出 → Static Hoisting\n  ↓\n動的ノードの検出 → Patch Flags 付与\n  ↓\nBlock 境界の特定 → openBlock/createBlock 挿入\n\n[実行時]\nopenBlock() → currentBlock = []\n  ↓\ncreateVNode (静的) → currentBlock に追加しない\n  ↓\ncreateVNode (動的) → currentBlock.push(vnode)\n  ↓\ncreateBlock() → vnode.dynamicChildren = currentBlock\n\n[更新時]\npatchElement(n1, n2)\n  ↓\nn2.dynamicChildren が存在？\n  ↓ Yes\npatchBlockChildren(n1.dynamicChildren, n2.dynamicChildren)\n  ↓\n動的ノードのみをパッチ\n```\n\n## まとめ\n\nTree Flattening（Block Tree）の実装は以下の要素で構成されています：\n\n1. **dynamicChildren**: 動的な子ノードを収集する配列\n2. **openBlock / createBlock**: Block の作成とトラッキング\n3. **patchBlockChildren**: 動的ノードのみをパッチ\n4. **Block 境界の管理**: `v-if`, `v-for` などで新しい Block を作成\n\nこの最適化により，Vue 3 は大規模なアプリケーションでも高速な更新を実現しています．Static Hoisting と Patch Flags と組み合わせることで，テンプレートベースのフレームワークならではの最適化が可能になっています．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/050-vapor/010-introduction.md",
    "content": "# Vapor Mode\n\n## Vapor Mode とは\n\nVapor Mode は Vue.js の新しいコンパイル戦略で，仮想 DOM を使用せずに直接 DOM 操作を行うことでパフォーマンスを向上させるアプローチです．\n\n従来の Vue.js では，コンポーネントの状態が変更されると，仮想 DOM を再生成し，差分検出（diffing）を行い，実際の DOM に反映するという流れでした．Vapor Mode では，この仮想 DOM のオーバーヘッドを排除し，リアクティブな値の変更時に必要な DOM 操作のみを直接実行します．\n\n## 詳細なリソース\n\nVapor Mode の詳細な解説については，以下のリポジトリを参照してください：\n\n**[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor)**\n\nこのリポジトリでは，Vue.js の Vapor Mode の内部実装について詳しく解説しています．\n\n## chibivue での Vapor 実装\n\nchibivue では，`runtime-vapor` パッケージで最小限の Vapor 実装を提供しています．\nここでは，その基本的なコンセプトを理解するための簡単な実装を見ていきましょう．\n\n### 基本的なアイデア\n\nVapor Mode の核心は以下の 2 点です：\n\n1. **テンプレートを DOM に直接変換する**: 仮想 DOM ノードではなく，実際の DOM 要素を生成する\n2. **リアクティブな値の変更を直接 DOM に反映する**: 差分検出なしに，変更された部分だけを更新する\n\n### template 関数\n\nまず，HTML 文字列から DOM 要素を作成する `template` 関数を見てみましょう：\n\n```ts\nexport type VaporNode = Element & { __is_vapor: true };\n\nexport const template = (tmp: string): VaporNode => {\n  const container = document.createElement(\"div\");\n  container.innerHTML = tmp;\n  const el = container.firstElementChild as VaporNode;\n  el.__is_vapor = true;\n  return el;\n};\n```\n\nこの関数は，HTML 文字列を受け取り，実際の DOM 要素を返します．仮想 DOM を介さず，直接 DOM を操作します．\n\n### setText 関数\n\nテキストコンテンツを更新する `setText` 関数です：\n\n```ts\nexport const setText = (\n  target: Element,\n  format: string,\n  ...values: any[]\n): void => {\n  const fmt = (): string => {\n    let text = format;\n    for (let i = 0; i < values.length; i++) {\n      text = text.replace(\"{}\", values[i]);\n    }\n    return text;\n  };\n\n  if (!target) return;\n\n  if (!values.length) {\n    target.textContent = fmt();\n    return;\n  }\n\n  if (!format && values.length) {\n    target.textContent = values.join(\"\");\n    return;\n  }\n\n  target.textContent = fmt();\n};\n```\n\nこの関数は，リアクティブな値が変更されたときに呼び出され，DOM のテキストコンテンツを直接更新します．\n\n### on 関数\n\nイベントリスナーを登録する `on` 関数です：\n\n```ts\nexport const on = (\n  element: Element,\n  event: string,\n  callback: () => void\n): void => {\n  element.addEventListener(event, callback);\n};\n```\n\n### Vapor コンポーネント\n\nVapor Mode でのコンポーネントは，通常の Vue コンポーネントとは異なる形式を取ります：\n\n```ts\nexport type VaporComponent = (self: VaporComponentInternalInstance) => VaporNode;\n\nexport interface VaporComponentInternalInstance {\n  __is_vapor: true;\n  uid: number;\n  type: VaporComponent;\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null;\n  appContext: AppContext;\n  provides: Data;\n  isMounted: boolean;\n  // ライフサイクルフック\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  // ...\n}\n```\n\nVapor コンポーネントは，インスタンスを受け取って VaporNode（実際の DOM 要素）を返す関数です．\n\n### コンパイル結果の比較\n\n従来の仮想 DOM ベースのコンパイル結果：\n\n```ts\n// 入力: <div>{{ count }}</div>\n// 仮想 DOM 出力\nfunction render(_ctx) {\n  return h(\"div\", null, _ctx.count);\n}\n```\n\nVapor Mode のコンパイル結果：\n\n```ts\n// 入力: <div>{{ count }}</div>\n// Vapor 出力\nconst t0 = template(\"<div></div>\");\n\nfunction render(_ctx) {\n  const el = t0();\n  effect(() => {\n    setText(el, _ctx.count);\n  });\n  return el;\n}\n```\n\nVapor Mode では：\n- テンプレートは事前に DOM 要素として生成される（`template` 関数）\n- リアクティブな値の更新は `effect` 内で直接 DOM を操作する\n- 仮想 DOM の生成と差分検出のコストがない\n\n## まとめ\n\nVapor Mode は，仮想 DOM のオーバーヘッドを排除することで，パフォーマンスを向上させる新しいアプローチです．chibivue の `runtime-vapor` パッケージでは，この概念の最小限の実装を提供しています．\n\nより詳細な実装や，Vue.js 本家の Vapor Mode については，[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor) を参照してください．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/050-vapor/020-vapor-compiler.md",
    "content": "# Vapor Compiler\n\n前のセクションでは，Vapor Mode の基盤となるランタイム関数（`template`, `setText`, `on`）を見てきました．\nこのセクションでは，これらの関数を使ったコードをテンプレートから自動生成するコンパイラを実装してみましょう．\n\n## Vapor コンパイラの目標\n\nVapor コンパイラの目標は，このようなテンプレートを：\n\n```html\n<button @click=\"count++\">{{ count }}</button>\n```\n\nこのようなコードに変換することです：\n\n```ts\nimport { template as _template, setText as _setText, on as _on, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  _renderEffect(() => {\n    _setText(_el0, \"\", count.value);\n  });\n  _on(_root, \"click\", () => count++);\n  return _root;\n})\n```\n\nポイントは以下の通りです：\n\n1. **静的な部分はテンプレート文字列になる**: HTML 構造は文字列として事前に作成される\n2. **動的な部分は renderEffect で処理される**: リアクティブな値の変更が直接 DOM 更新をトリガーする\n3. **イベントハンドラは直接アタッチされる**: 仮想 DOM のイベント委譲なし\n\n## コンパイラのアーキテクチャ\n\nVapor コンパイラは，通常のテンプレートコンパイラと同様のパイプラインに従いますが，中間表現（IR）を使ったより洗練されたアプローチを採用しています：\n\n```\nテンプレート (string)\n  ↓ [Parse]\nAST (Abstract Syntax Tree)\n  ↓ [Transform]\nIR (Intermediate Representation)\n  ↓ [Codegen]\nVapor コード (string)\n```\n\n## IR (Intermediate Representation) とは\n\nIR（中間表現）は，AST と最終的なコードの間に位置するデータ構造です．\nIR を使う利点は以下の通りです：\n\n1. **関心の分離**: パースとコード生成を明確に分離できる\n2. **最適化の容易さ**: IR レベルで静的解析や最適化が行いやすい\n3. **拡張性**: 新しい機能の追加が容易\n\n### IR の構造\n\n```ts\n// IR ノードの種類\nenum IRNodeTypes {\n  ROOT = \"root\",\n  BLOCK = \"block\",\n  SET_TEXT = \"setText\",\n  SET_EVENT = \"setEvent\",\n  SET_PROP = \"setProp\",\n  IF = \"if\",\n  FOR = \"for\",\n}\n\n// ブロック IR ノード - 操作とエフェクトのコンテナ\ninterface BlockIRNode {\n  type: IRNodeTypes.BLOCK;\n  node: RootNode | TemplateChildNode;\n  dynamic: IRDynamicInfo;\n  effect: IREffect[];      // リアクティブな操作\n  operation: OperationNode[]; // 非リアクティブな操作\n  returns: number[];       // 返す要素の ID\n}\n\n// エフェクト - リアクティブな依存関係と操作のセット\ninterface IREffect {\n  expressions: SimpleExpressionNode[]; // 依存する式\n  operations: OperationNode[];         // 実行する操作\n}\n```\n\n### 操作ノードの例\n\n```ts\n// テキスト更新\ninterface SetTextIRNode {\n  type: IRNodeTypes.SET_TEXT;\n  element: number;  // 要素 ID\n  values: SimpleExpressionNode[];\n}\n\n// イベントバインディング\ninterface SetEventIRNode {\n  type: IRNodeTypes.SET_EVENT;\n  element: number;\n  key: string;      // イベント名\n  value: SimpleExpressionNode;\n  modifiers?: string[];\n}\n\n// プロパティバインディング\ninterface SetPropIRNode {\n  type: IRNodeTypes.SET_PROP;\n  element: number;\n  key: string;\n  value: SimpleExpressionNode;\n}\n```\n\n## Transformer の役割\n\nTransformer は AST を IR に変換するコンポーネントです．\n各 AST ノードを走査し，適切な IR ノードを生成します．\n\n### TransformContext\n\n```ts\ninterface TransformContext {\n  root: RootIRNode;\n  block: BlockIRNode;\n  template: string;        // 静的テンプレート文字列\n  elementCount: number;\n\n  // 要素 ID を割り当て\n  reference(): number;\n\n  // リアクティブなエフェクトを登録\n  registerEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void;\n\n  // 非リアクティブな操作を登録\n  registerOperation(...operations: OperationNode[]): void;\n\n  // 新しいブロックに入る（v-if, v-for 用）\n  enterBlock(block: BlockIRNode): () => void;\n}\n```\n\n### 変換の流れ\n\n```ts\nexport function transform(ast: RootNode, source: string): RootIRNode {\n  const ir = createRootIR(ast, source);\n  const context = createTransformContext(ir);\n\n  // 子要素を再帰的に変換\n  transformChildren(ast.children, context);\n\n  // テンプレート文字列を保存\n  ir.template.push(context.template);\n\n  return ir;\n}\n```\n\n### ディレクティブの変換\n\n各ディレクティブは専用の変換関数で処理されます：\n\n```ts\n// v-on の変換\nfunction transformVOn(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const eventName = (dir.arg as SimpleExpressionNode).content;\n\n  // イベントは operation として登録（リアクティブではない）\n  context.registerOperation({\n    type: IRNodeTypes.SET_EVENT,\n    element: elementId,\n    key: eventName,\n    value: dir.exp as SimpleExpressionNode,\n    modifiers: dir.modifiers,\n  });\n}\n\n// v-bind の変換\nfunction transformVBind(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const propName = (dir.arg as SimpleExpressionNode).content;\n\n  // effect として登録（リアクティブ）\n  context.registerEffect([dir.exp as SimpleExpressionNode], [\n    {\n      type: IRNodeTypes.SET_PROP,\n      element: elementId,\n      key: propName,\n      value: dir.exp as SimpleExpressionNode,\n    },\n  ]);\n}\n```\n\n### 定数式の最適化\n\n`registerEffect` は式が定数かどうかをチェックし，定数の場合は `effect` ではなく通常の `operation` として登録します：\n\n```ts\nregisterEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void {\n  // 定数式をフィルタリング\n  const reactiveExpressions = expressions.filter((exp) => !isConstantExpression(exp));\n\n  // リアクティブな依存がなければ operation として登録\n  if (reactiveExpressions.length === 0) {\n    context.registerOperation(...operations);\n    return;\n  }\n\n  // effect として登録\n  currentBlock.effect.push({\n    expressions: reactiveExpressions,\n    operations,\n  });\n}\n```\n\n## renderEffect とは\n\n`renderEffect` は Vapor Mode の核心となる関数です．\n仮想 DOM の差分検出アプローチとは異なり，リアクティブな依存関係を直接追跡し，変更時に DOM を更新します．\n\n### 仕組み\n\n```ts\n/**\n * renderEffect - Vapor Mode でのリアクティブな DOM 更新のコアメカニズム\n *\n * 1. DOM 更新関数をリアクティブな effect でラップ\n * 2. アクセスされたリアクティブな値を自動的に追跡\n * 3. 追跡された値が変更されたら更新関数を再実行\n * 4. 変更が必要な特定の DOM ノードのみを更新\n *\n * 重要: renderEffect はライフサイクルフックも処理します:\n * - 各更新前に onBeforeUpdate フックを呼び出す（初回マウント後）\n * - 各更新後に onUpdated フックを呼び出す（初回マウント後）\n */\nexport const renderEffect = (fn: () => void): void => {\n  const instance = currentInstance;\n\n  effect(() => {\n    // 更新前: onBeforeUpdate フックを呼び出す（マウント後のみ）\n    if (instance?.isMounted) {\n      const { bu } = instance;\n      if (bu) invokeArrayFns(bu);\n    }\n\n    // 更新を実行\n    fn();\n\n    // 更新後: onUpdated フックを呼び出す（マウント後のみ）\n    if (instance?.isMounted) {\n      const { u } = instance;\n      if (u) {\n        queueMicrotask(() => invokeArrayFns(u));\n      }\n    }\n  });\n};\n```\n\n### 生成されるコードの例\n\n```ts\n// テンプレート: <span>{{ count }}</span>\n\nrenderEffect(() => {\n  setText(_el0, \"\", count.value)\n})\n\n// count.value が変更されると:\n// 1. onBeforeUpdate フックが呼び出される\n// 2. テキスト内容が更新される\n// 3. onUpdated フックが呼び出される（マイクロタスクで）\n```\n\n### 仮想 DOM との比較\n\n| 観点 | 仮想 DOM | Vapor (renderEffect) |\n|------|----------|---------------------|\n| 更新粒度 | コンポーネント全体を再レンダー | 変更された部分のみ更新 |\n| 追跡方法 | 差分アルゴリズム | リアクティブな依存追跡 |\n| オーバーヘッド | VNode の作成と比較 | なし（直接 DOM 操作） |\n\n## Codegen の実装\n\nCodegen は IR からコードを生成します：\n\n```ts\nexport function generateVaporFromIR(ir: RootIRNode, options = {}): VaporCodegenResult {\n  const context = createVaporCodegenContext();\n\n  // preamble（import 文など）を生成\n  genVaporPreamble(context, options.isBrowser);\n\n  // コンポーネント関数を生成\n  push(`((_self) => {`);\n  indent();\n\n  // template() 呼び出しを生成\n  push(`const _root = _template(\\`${ir.template[0]}\\`);`);\n\n  // 要素参照を生成\n  for (let i = 0; i < elementCount; i++) {\n    push(`const _el${i} = _root${generateElementPath(i)};`);\n  }\n\n  // 非リアクティブな操作を生成\n  for (const op of block.operation) {\n    genOperation(op, context);\n  }\n\n  // リアクティブなエフェクトを生成\n  for (const effect of block.effect) {\n    push(`_renderEffect(() => {`);\n    indent();\n    for (const op of effect.operations) {\n      genOperation(op, context);\n    }\n    deindent();\n    push(`});`);\n  }\n\n  push(`return _root;`);\n  deindent();\n  push(`})`);\n\n  return { code: context.code, preamble, ast: ir.node };\n}\n```\n\n## 使用例\n\n```ts\nimport { compile } from \"@chibivue/compiler-vapor\";\n\n// IR ベースのコンパイル\nconst result = compile(`\n  <button @click=\"count++\" :class=\"btnClass\">Count: {{ count }}</button>\n`, { useIR: true });\n\nconsole.log(result.code);\n```\n\n出力：\n\n```ts\nimport { template as _template, setText as _setText, on as _on, setClass as _setClass, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  const _el1 = _root;\n  _on(_el1, \"click\", count++);\n  _renderEffect(() => {\n    _setClass(_el1, btnClass);\n  });\n  _renderEffect(() => {\n    _setText(_el0, \"\", count);\n  });\n  return _root;\n})\n```\n\n## まとめ\n\nVapor コンパイラは，以下のパイプラインでテンプレートをコードに変換します：\n\n1. **Parse**: テンプレートを AST に変換\n2. **Transform**: AST を IR に変換（最適化を含む）\n3. **Codegen**: IR からコードを生成\n\nIR を使うことで：\n- 静的解析と最適化が容易に\n- コードの保守性が向上\n- 新機能の追加が簡単に\n\n`renderEffect` により：\n- きめ細かなリアクティブ更新が可能\n- 仮想 DOM のオーバーヘッドを排除\n- 変更された部分のみを効率的に更新\n\n次のセクションでは，SSR サポートによってサーバー上で Vapor コンポーネントをレンダリングする方法を見ていきます．\n"
  },
  {
    "path": "book/online-book/src/ja/90-web-application-essentials/050-vapor/030-vapor-ssr.md",
    "content": "# Vapor SSR\n\nこのセクションでは，サーバーサイドで Vapor コンポーネントをレンダリングする方法を探ります．\nVapor コンポーネントは DOM を直接操作しますが，サーバー上には DOM が存在しないため，Vapor の SSR（Server-Side Rendering）には独自の課題があります．\n\n## 課題\n\nVapor コンポーネントは以下のように動作します：\n1. `document.createElement` を使って DOM 要素を作成（`template()` 経由）\n2. `textContent` や `addEventListener` などでそれらの要素を直接操作\n\nサーバー上には `document` オブジェクトがありません．Vapor コンポーネントから HTML 文字列を生成するには，別のアプローチが必要です．\n\n## 解決アプローチ\n\nVapor SSR には主に 2 つのアプローチがあります：\n\n1. **Mock DOM**: DOM 操作をキャプチャして HTML に変換する疑似 DOM 環境を作成\n2. **VNode SSR の再利用**: サーバーサイドでは通常の VNode ベースの SSR を使用し，クライアントで Vapor としてハイドレート\n\nVue.js の [PR #13226](https://github.com/vuejs/core/pull/13226) では，2 番目のアプローチが採用されています．chibivue でも同様のアプローチを実装しています．\n\n<KawaikoNote variant=\"base\" title=\"Vue.js のアプローチ\">\nVue.js の Vapor SSR は，サーバーサイドでは既存の VNode ベースの SSR（compiler-ssr）を使用し，クライアントサイドでは `createVaporSSRApp` を使ってハイドレーションを行います．これにより，SSR のための別のコンパイラを作成する必要がなくなります．\n</KawaikoNote>\n\n## 実装方式\n\n### サーバーサイド: VNode SSR の利用\n\nVapor SSR では，サーバーサイドで Vapor コンポーネントを通常の VNode ベースのコンポーネントとしてコンパイルします．これにより `@chibivue/compiler-ssr` がそのまま使用できます．\n\n```ts\n// compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  ssr = false,\n  vapor = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  // Vapor + SSR モードでも compiler-ssr を使用\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = defaultCompiler.compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n\n  // Vapor + SSR モードでは __vapor フラグを追加\n  if (vapor && ssr) {\n    code = code.replace(\n      /export (function|const) ssrRender/,\n      \"export const __vapor = true;\\nexport $1 ssrRender\",\n    );\n  }\n\n  return { code, ast, source, preamble };\n}\n```\n\n`__vapor` フラグは，ハイドレーション時に Vapor モードを使用することを示します．\n\n### クライアントサイド: createVaporSSRApp\n\nクライアントサイドでは，`createVaporSSRApp` を使用して SSR でレンダリングされた HTML をハイドレートします．\n\n```ts\n// runtime-vapor/src/apiCreateVaporApp.ts\nexport function createVaporSSRApp(rootComponent: VaporComponent): VaporApp {\n  const context = createAppContext();\n\n  const app: VaporApp = {\n    // ... 共通のアプリ設定 ...\n\n    mount(containerOrSelector: Element | string) {\n      const container = typeof containerOrSelector === \"string\"\n        ? document.querySelector(containerOrSelector)\n        : containerOrSelector;\n\n      if (container?.hasChildNodes()) {\n        // SSR コンテンツが存在する場合はハイドレーション\n        const vnode = createVNode(rootComponent as any);\n        vnode.appContext = context;\n        const instance = hydrateVaporComponent(vnode, container, null);\n        app._instance = instance;\n      } else {\n        // SSR コンテンツがない場合は通常のマウント\n        // ...\n      }\n    },\n  };\n\n  return app;\n}\n```\n\n### ハイドレーション\n\nハイドレーションプロセスでは，既存の DOM 要素を再利用しながらリアクティビティとイベントリスナーを設定します．\n\n```ts\n// runtime-vapor/src/hydration.ts\nexport function hydrateVaporComponent(\n  vnode: VNode,\n  container: Element,\n  parentInstance: VaporComponentInternalInstance | null = null,\n): VaporComponentInternalInstance {\n  const instance = createVaporComponentInstance(vnode, parentInstance);\n\n  // ハイドレーションコンテキストを設定\n  const ctx: VaporHydrationContext = {\n    node: container.firstChild,\n    parent: container,\n  };\n\n  setCurrentInstance(instance as any);\n  (instance as any).__hydrationCtx = ctx;\n\n  try {\n    const comp = instance.type as VaporComponent;\n    // コンポーネントを実行 - template() は既存の DOM を見つける\n    const el = comp(instance);\n\n    // マウント完了\n    instance.isMounted = true;\n\n    // mounted フックを呼び出し\n    const { m } = instance as any;\n    if (m) invokeArrayFns(m);\n\n    return instance;\n  } finally {\n    unsetCurrentInstance();\n    delete (instance as any).__hydrationCtx;\n  }\n}\n```\n\n## Mock DOM アプローチ\n\nchibivue では Mock DOM アプローチも `server-renderer` に実装しています．これは VNode SSR を使わない場合のフォールバックとして機能します．\n\n### SSR Elements\n\nDOM 要素を模倣しつつ，データをメモリに保存するクラスを作成します：\n\n```ts\nclass SSRElement {\n  tagName: string;\n  attributes: Map<string, string> = new Map();\n  children: (SSRElement | SSRText)[] = [];\n  textContent: string = \"\";\n\n  constructor(tagName: string) {\n    this.tagName = tagName.toLowerCase();\n  }\n\n  setAttribute(name: string, value: string): void {\n    this.attributes.set(name, value);\n  }\n\n  addEventListener(): void {\n    // SSR では何もしない - イベントはクライアントサイドのみ\n  }\n\n  appendChild(child: SSRElement | SSRText): void {\n    this.children.push(child);\n  }\n\n  toHTML(): string {\n    let html = `<${this.tagName}`;\n    for (const [name, value] of this.attributes) {\n      html += ` ${name}=\"${escapeHtml(value)}\"`;\n    }\n    html += \">\";\n\n    if (this.textContent) {\n      html += escapeHtml(this.textContent);\n    } else {\n      for (const child of this.children) {\n        html += child.toHTML();\n      }\n    }\n\n    html += `</${this.tagName}>`;\n    return html;\n  }\n}\n```\n\n## 使用例\n\n### サーバーサイド\n\n```ts\nimport { createVNode } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport App from \"./App.vue\";\n\n// コンポーネントを HTML 文字列にレンダリング\nconst html = await renderToString(createVNode(App));\n\n// HTML レスポンスを送信\nres.send(`\n<!DOCTYPE html>\n<html>\n  <head><title>My App</title></head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/entry-client.ts\"></script>\n  </body>\n</html>\n`);\n```\n\n### クライアントサイド\n\n```ts\n// entry-client.ts\nimport { createVaporSSRApp } from \"@chibivue/runtime-vapor\";\nimport App from \"./App.vue\";\n\n// SSR でレンダリングされた HTML をハイドレート\ncreateVaporSSRApp(App).mount(\"#app\");\n```\n\n## 仮想 DOM SSR との比較\n\n| 観点 | 仮想 DOM SSR | Vapor SSR |\n|--------|-----------------|-----------|\n| サーバーレンダリング | VNode ツリーを走査し，HTML を生成 | 同じ（VNode SSR を利用） |\n| クライアントハイドレーション | VNode diff を使用 | DOM を直接参照・操作 |\n| バンドルサイズ | 仮想 DOM ランタイムが必要 | 軽量な Vapor ランタイム |\n| 更新パフォーマンス | diff アルゴリズムを経由 | 直接 DOM 操作 |\n\n## アーキテクチャの利点\n\nVue.js スタイルの Vapor SSR アプローチには以下の利点があります：\n\n1. **コードの再利用**: 既存の `compiler-ssr` をそのまま使用可能\n2. **一貫した出力**: サーバーで生成される HTML は通常の VNode SSR と同一\n3. **段階的な移行**: Vapor を使わないコンポーネントと混在可能\n4. **保守性**: SSR 用の別コンパイラを維持する必要がない\n\n<KawaikoNote variant=\"warning\" title=\"ハイドレーションが必要\">\nサーバーでレンダリングされた HTML は静的です．インタラクティビティを得るには，クライアントサイドで Vapor コンポーネントをハイドレートする必要があります．これにより，リアクティブ effect とイベントリスナーが設定されます．\n</KawaikoNote>\n\n## 制限事項\n\n現在の実装は最小限であり，いくつかの制限があります：\n\n1. **ストリーミング非対応**: コンポーネント全体がレンダリングされてから返される\n2. **Suspense 非対応**: 非同期コンポーネントの SSR サポートは限定的\n3. **ハイドレーションミスマッチ**: クライアントとサーバーの出力が異なる場合の警告機能は未実装\n\n<KawaikoNote variant=\"base\" title=\"将来の改善\">\nより完全な実装には以下が含まれます：\n- ストリーミング SSR サポート\n- ハイドレーションミスマッチ検出\n- Suspense との統合\n</KawaikoNote>\n\n## まとめ\n\nVapor SSR は以下のように動作します：\n\n1. **サーバーサイド**: `compiler-ssr` を使用して HTML 文字列を生成（VNode SSR と同じ）\n2. **クライアントサイド**: `createVaporSSRApp` を使用してハイドレーション\n3. **ハイドレーション**: 既存の DOM 要素を再利用しながらリアクティビティを設定\n\nこのアプローチにより，Vapor コンポーネントは SSR のメリットを享受しながら，クライアントサイドでは直接 DOM 操作のパフォーマンスメリットを得ることができます．\n"
  },
  {
    "path": "book/online-book/src/ja/bonus/debug-vuejs-core.md",
    "content": "# 本家のソースコードをデバッグする\n\n実際に本家の Vue.js のコードを動かして動作を確かめたい場合があるかと思います．  \nこの本の方針としても是非とも本家のソースコード読みながら理解できるようになってほしいところもあり，ソースコードリーディングやテストプレイを強く推奨しています．\n\nそこで，本編では触れていない本家のソースコードのデバッグ方法をいくつか紹介します．\n\n(手軽な順番で紹介していきます．)\n\n## SFC Playground を活用する\n\nこれは最も手軽な方法です．公式ドキュメントからもリンクされているほど，広く知られている方法です．\n\nhttps://play.vuejs.org\n\nこのプレイグラウンドでは Vue のコンポーネントを記述しながら動作を確認することはもちろん，SFC のコンパイル結果を確認できます．  \nサクッとブラウザ上で確認できるので便利です．(もちろん共有もできます．)\n\n<video src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/8281e589-fdaf-4206-854e-25a66dfaac05\" controls />\n\n## vuejs/core のテストを活用する\n\n続いては [vuejs/core](https://github.com/vuejs/core) のテストを実行してみる方法です．\n当然ですが，これはもちろん [vuejs/core](https://github.com/vuejs/core) のソースコードを clone してくる必要があります．\n\n```bash\ngit clone https://github.com/vuejs/core.git vuejs-core\n# NOTE: `core` というリポジトリ名になっているので、わかりやすくしておくのがおすすめです\n```\n\nあとは，\n\n```bash\ncd vuejs-core\nni\npnpm test\n```\n\nでテストを実行する事ができるので，適宜気になるソースコードをいじってみてテストを実行してみましょう．\n\n`test` 以外にもいくつかテストコマンドがあるので，気になる方は `package.json` を見てみてください．\n\nテストコードを読んで把握するもよし，実際にコードをいじってテストを走らせるもよし，テストケースを追加してみるもよし，色々な使い方ができます．\n\n<img width=\"590\" alt=\"スクリーンショット 2024-01-07 0 31 29\" src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/3c862bd5-1d94-4d2a-a9fa-8755872098ed\">\n\n## vuejs/core のソースコードを実際に動かしてみる\n\n続いては，一番手軽ではないのですがやはり vuejs/core のソースコードを実際にいじりながら動作させる方法です．\n\nこちらに関しては, SFC, standalone ともに vite で HMR できるプロジェクトを用意しているので，ぜひそちらを使ってみてください．\nこのプロジェクトは [chibivue](https://github.com/chibivue-land/chibivue) のリポジトリにあるので clone してください．\n\n```bash\ngit clone https://github.com/chibivue-land/chibivue.git\n```\n\nclone できたら，プロジェクトを作成するスクリプトを実行します．\n\nこの際，ローカルにある vuejs/core のソースコードの**絶対パス**を求められるはずなので，入力してください．\n\n```bash\ncd chibivue\nni\npnpm setup:vue\n\n# 💁 input your local vuejs/core absolute path:\n#   e.g. /Users/ubugeeei/oss/vuejs-core\n#   >\n```\n\nこれで chibivue のリポジトリ内に ローカルの vuejs/core を指すような vue のプロジェクトが作成されます．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/5d57c022-c411-4452-9e7e-c27623ec28b4\" controls/>\n\nあとは起動したい時に以下のコマンドで起動して，vuejs/core のソースコードをいじりながら動作を確認する事ができます．\n\n```bash\npnpm dev:vue\n```\n\nplayground 側の HMR はもちろん，\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/a2ad46d8-4b07-4ac5-a887-f71507c619a6\" controls/>\n\nvuejs/core のコードをいじっても HMR が効きます．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/72f38910-19b8-4171-9ed7-74d1ba223bc8\" controls/>\n\n---\n\nまた，standalone で確認したい際は index.html で standalone-vue.js の方を読み込むように変更するとこちらも HMR で確認できます．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/c57ab5c2-0e62-4971-b1b4-75670d3efeec\" controls/>\n"
  },
  {
    "path": "book/online-book/src/ja/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl.md",
    "content": "# Hyper Ultimate Super Extreme Minimal Vue\n\n## プロジェクトのセットアップ (0.5 min)\n\n```sh\n# 本リポジトリをクローンして移動しましょう。\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\n\n# setup コマンドでプロジェクトを作成します。\n# 引数にはプロジェクトのルートパスを指定します。\npnpm setup ../my-chibivue-project\n```\n\nこれでプロジェクトの設定はおしまいです．\n\nここからは packages/index.ts を実装していきましょう．\n\n## createApp (1 min)\n\ncreate app には setup 関数と render 関数を指定できるようなシグネチャを考えます．\nユーザーからすると，\n\n```ts\nconst app = createApp({\n  setup() {\n    // TODO:\n  },\n  render() {\n    // TODO:\n  },\n})\n\napp.mount('#app')\n```\n\nのように使うイメージですね．\n\n実装していきます．\n\n```ts\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\n```\n\nこれを受け取って，とりあえず mount 関数を実装したオブジェクトを return するようなものにすれば OK です．\n\n```ts\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    // TODO: patch rendering\n  },\n})\n```\n\nはい．これでおしまいです．\n\n## h 関数と仮想 DOM (0.5 min)\n\npatch レンダリングを行いたいですが，そのためには仮想 DOM とそれを生成するための関数が必要です．\n\n仮想 DOM というのは タグ名や属性，子要素などの情報を JS のオブジェクトで表現したもので，  \nVue の renderer は基本的にはこの仮想 DOM を扱いながら実 DOM への反映を行っていきます．\n今回は名前と click イベントのハンドラと 子要素( text )を扱うような VNode を考えてみます．\n\n```ts\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n```\n\nはい．お終いです．\n\n## patch rendering (2 min)\n\nそれでは renderer を実装していきます．\n\nこのレンダリング処理はたちまち patch 処理と呼ばれたりしますが， patch という名の通り，\n\n新旧の仮想 DOM を比較して差分を実 DOM に反映します．\n\nつまり，関数のシグネチャ的には\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  // TODO:\n}\n```\n\nのようになります．  \nn1 が古い VNode, n2 が新しい VNode, container というのは実 DOM の root です．  \n今回の例で言うと `#app` が container になります．(createApp で mount した要素)\n\n中身の実装について，考慮するべきは 2 種類の処理です．\n\n- mount  \n  初回です． n1 が null の場合に初回レンダリングという判断を行ってマウント処理を書きます．\n- patch  \n  VNode 同士で比較して差分を実 DOM に反映します．  \n  とはいっても，今回は children を更新するだけで，差分の検知は行いません．\n\nそれでは実装してみます．\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n```\n\n以上になります．\n\n## Reactivity System (2 min)\n\nこれからは実際に setup オプションでセットアップされたステートの変更を追跡して，\n\nrender 関数を発火させる処理を実装していきます．ステートの更新を追跡して特定の作用を実行することから「Reactivity System」というふうな名前がついています．\n\n今回は `reactive` という関数でユーザーにステートを定義することを考えてみます．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n  // ..\n  // ..\n})\n```\n\nこのようなイメージです．\n実際に，この reactive 関数で定義されたステートが変更された際に patch 処理を実行したいです．\n\nこれは Proxy というオブジェクトを用いて実現されます．\nProxy は get / set に対して機能を実装することができます．今回はこの set に対する拡張を利用して， set 時に patch 処理を実行するように実装してみます．\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      // ??? ここで patch 処理を実行したい\n      return res\n    },\n  })\n```\n\n問題としては，set で何を発火するかです．\n本来は get によって作用を track したりしなければならないのですが，今回はグローバルなスコープに update 関数を定義してそれを参照します．\n\n先ほど実装した render 関数を使って update 関数を実装してみます．\n\n```ts\nlet update: (() => void) | null = null // Proxy で参照したいのでグローバルに\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup() // 初回のみ setup\n    update = () => {\n      // prevVNode と VNode を比較できるようにいい感じにクロージャを生成している。\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n```\n\nはい．あとは Proxy の set で呼んであげましょう．\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.() // 実行\n      return res\n    },\n  })\n```\n\n## template compiler (5 min)\n\nここまでで，ユーザーに render オプションと h 関数を使わせて 宣言的な UI を実装できるようにはなったのですが，\n実際には HTML ライクに記述したいです．\n\nそこで，HTML から h 関数に変換するような template compiler を実装してみます．\n\n目標的には，\n\n```\n<button @click=\"increment\">state: {{ state.count }}</button>\n```\n\nのような文字列を，\n\n```\nh(\"button\", increment, \"state: \" + state.count)\n```\n\nのような関数に変換したいです．\n\n少し段階分けをします．\n\n- parse  \n  HTML の文字列を解析し，AST と呼ばれるオブジェクトに変換します．\n- codegen  \n  AST を元に目標のコード (文字列) を生成します．\n\nそれでは，AST と parse を実装してみます．\n\n```ts\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\n```\n\n今回扱う AST は上記の通りです． VNode と似ていますが全くの別物で，これはコードを生成するためのものです．\nInterpolation というのがマスタッシュ構文です． <span v-pre>`{{ state.count }}`</span> のような文字列は， <span v-pre>`{ content: \"state.count\" }`</span> というオブジェクト(AST)に解析されます．\n\nあとは与えられた文字列から AST を生成する parse 関数を実装してしまえば OK です．\nこちらは取り急ぎ，正規表現といくつかの文字列操作で実装してみます．\n\n```ts\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\n```\n\n次に codegen です． AST を元に h 関数の呼び出しを生成します．\n\n```ts\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\n```\n\nstate には \\_ctx という引数から参照するようにしています．\n\nこれらを組み合わせれば compile 関数の完成です．\n\n```ts\nconst compile = (template: string): string => codegen(parse(template))\n```\n\nまあ，実はこのままではただ h 関数の呼び出しを文字列として生成するだけなので，まだ動かないのですが，\n\nそれは次の sfc compiler で一緒に実装してしまいます．\n\nこれで template compiler は完成です．\n\n## sfc compiler (vite-plugin) (4 min)\n\nラスト！ vite のプラグインを実装して sfc に対応していきます．\n\nvite のプラグインには，transform というオプションがあり，これを使うとファイルの内容を変換することができます．\n\ntransform 関数は `{ code: string }` のようなものを return することで，その文字列がソースコードとして扱われます．\nつまり，例えば，\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: \"vite-plugin-chibivue\",\n  transform: (code: string, id: string) => ({\n    code: \"\";\n  }),\n});\n```\n\nのようにすれば，全てのファイルの内容が空文字列になります．\n元々のコードは第一引数で受け取れるようになっているので，この値をうまく変換して最後に return すれば変換することができます．\n\nやることは， 5 つです．\n\n- script から default export されているものを抜き出す．\n- それを変数に入れるようなコードに変換する．(便宜上その変数名を A とします．)\n- template から HTML 文字列を抜き出して，さっき作った compile 関数で h 関数の呼び出しに変換する． (便宜上その結果を B とします．)\n- `Object.assign(A, { render: B })` というようなコードを生成する．\n- A を default export するようなコードを生成する．\n\nそれでは実装してみましょう．\n\n```ts\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\nあとはこれを Plugin に実装してあげれば Ok です．\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : code, // 拡張子が .vue の場合のみ\n})\n```\n\n## おしまい\n\nはい．なんとこれで SFC まで実装することができました．\n改めてソースコードを眺めてみましょう．\n\n```ts\n// create app api\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\nlet update: (() => void) | null = null\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup()\n    update = () => {\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n\n// Virtual DOM patch\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n\n// Virtual DOM\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n\n// Reactivity System\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.()\n      return res\n    },\n  })\n\n// template compiler\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\nconst compile = (template: string): string => codegen(parse(template))\n\n// sfc compiler (vite transformer)\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : null,\n})\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\nなんと 110 行くらいで実装できてしまいました．(これで誰からも文句言われないでしょう．ふぅ...)\n\n## ぜひ本編の本編の方もやってくださいね！！！！！！！！\n\nぜひ本編の本編の方もやってくださいね！！！！！！！！ (これはあくまで付録ですから)\n"
  },
  {
    "path": "book/online-book/src/ja/bonus/hyper-ultimate-super-extreme-minimal-vue/index.md",
    "content": "# chibivue？ どこが chibi なんだ！？デカすぎてやってらんないよ！？\n\n## 大きいじゃん......\n\nそう思った方，誠に申し訳ございません．\n\nこの本を手に取る前はもっと小さいものを想像していたかもしれません．\n\n少し言い訳させていただきますと，僕自身もこんなに大きくやるつもりはなかったんです．\n\nやってくうちについ楽しくて，「お，次はこの辺機能追加してみるか」とやっていったところ今のようになってしまいました．\n\n## わかりました．制限時間を設けましょう．\n\n大きくなりすぎてしまった要因の一つとして，「時間に制限がなかった」という点が挙げられます．\n\nそこで，この付録では 「**15 分**」で実装してみます．\n\n説明ももちろん 1 ページだけに収めます．\n\nさらに，ページのみならず，「実装自体も 1 ファイルに収める」ことを目標にやってみます．\n\nただ 1 ファイルといっても 1 ファイルに 10 万行書いてしまっては意味がないですから，150 行ないくらいを目標に実装していきます．\n\n![Full source of Hyper Ultimate Super Extreme Minimal Vue in one file](/figures/bonus/hyper-ultimate-super-extreme-minimal-vue/full-source-screenshot.png)\n\n題して，「**Hyper Ultimate Super Extreme Minimal Vue**」\n\n::: info 名前の由来について\n\nなんとも幼稚な名前だなと思った方が多いと思います．\n\n僕もそう思います．\n\nただ，この名前にはちゃんとした由来があります．\n\nとにかく小さいということを強調しつつ，略称は欲しかったので，この語順になってます．\n\nその略称というのが「HUSEM Vue (風船 Vue)」 です．\n\nこれからとっても雑な実装をしていきますが，その雑さを「風船」に例えています．\n少しでも針が触れると破裂してしまうようなイメージです．\n\n:::\n\n## どうせ リアクティブシステムをちょろっと実装するだけなんでしょ？\n\nいいえ，そうではありません．今回 15 分で対応するものを以下に列挙してみます．\n\n- create app api\n- Virtual DOM\n- patch rendering\n- Reactivity System\n- template compiler\n- sfc compiler (vite-plugin)\n\nこれくらいものを実装していきます．\n\nつまり，SFC が動きます．\n\nソースコードとしては，以下のようなものが動作する想定をしています．\n\n```vue\n<script>\nimport { reactive } from 'hyper-ultimate-super-extreme-minimal-vue'\n\nexport default {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"increment\">state: {{ state.count }}</button>\n</template>\n```\n\n```ts\nimport { createApp } from 'hyper-ultimate-super-extreme-minimal-vue'\n\n// @ts-ignore\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n"
  },
  {
    "path": "book/online-book/src/ja/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: \"chibivue\"\n  text: \"Step by Step, from just one line of \\\"Hello, World\\\".\"\n  tagline: powered by VitePress\n  image: /figures/_brand/logo.png\n  actions:\n    - theme: brand\n      text: Dive into book ->\n      link: /ja/00-introduction/010-about\n    - theme: alt\n      text: Vue.js Official\n      link: https://vuejs.org/\n\nfeatures:\n  - title: Reactivity System\n    details: 基本的なリアクティビティの原理から、effectScope や customRef などの応用的な API の実装まで幅広く行います。\n  - title: Virtual DOM\n    details:  Virtual DOM の基本的な実装から、パッチレンダリング、スケジューラの実装まで幅広く行います。\n  - title: Template Compiler\n    details: テンプレートコンパイラの基本的な実装から、データバインディングやディレクティブの実装まで幅広く行います。\n  - title: Single File Component\n    details: SFC の基本的な実装から、script setup やコンパイラマクロ、scoped css の実装まで幅広く行います。\n---\n"
  },
  {
    "path": "book/online-book/src/zh-cn/00-introduction/010-about.md",
    "content": "# 介绍\n\n## 本书的目的\n\n感谢您选择这本书！  \n如果您对这本书哪怕有一点点兴趣，我都感到非常高兴．  \n让我首先总结一下这本书的目的．\n\n**☆ 目的**\n\n- **深入理解 Vue.js**  \n  什么是 Vue.js？它是如何构建的？\n- **能够实现 Vue.js 的基本功能**  \n  实际尝试实现基本功能．\n- **阅读 vuejs/core 的源代码**  \n  理解实现与官方代码之间的关系，掌握它们是如何真正构建的．\n\n我提供了一个大致的目标概述，但没有必要完成所有目标，也不是要求追求完美．  \n无论您是从头到尾阅读，还是只挑选感兴趣的部分，都由您决定．  \n如果您发现这本书的哪怕一小部分有用，我都会很高兴！\n\n## 目标读者\n\n- **有 Vue.js 使用经验的人**\n- **能够编写 TypeScript**\n\n仅有这两个先决条件，不需要其他知识．  \n虽然您在阅读本书过程中可能会遇到不熟悉的术语，但我已经尽力排除任何先验知识，并在过程中进行解释，旨在使这本书自成体系．  \n但是，如果您遇到不应该用于 Vue.js 或 TypeScript 的术语，我建议您先从相应的资源中学习．  \n（基本功能就足够了！（不需要深入研究））\n\n## 本书（和作者）所关注的（并希望实现的）\n\n在深入之前，我想分享一些我在写这本书时特别关注的事情．  \n我希望您在阅读时记住这些，如果有任何我没有达到目标的地方，请告诉我．\n\n- **消除对先验知识的需求**  \n  虽然这可能与前面提到的\"目标读者\"部分重叠，但我努力使这本书尽可能自成体系，  \n  最大限度地减少对先验知识的需求，并根据需要提供解释．  \n  这是因为我想让尽可能多的读者理解尽可能清晰的解释．  \n  有丰富经验的人可能会发现一些解释有点冗长，但我请求您的理解．\n\n- **增量实现**  \n  本书的目标之一是手工增量实现 Vue.js．这意味着本书专注于实践方法，  \n  在实现方面，我强调以小的增量步骤构建．  \n  更具体地说，就是\"最小化非工作状态\"．  \n  而不是拥有直到完成才能工作的东西，目标是在每个阶段都保持其功能．  \n  这反映了我个人的编码方法——持续编写非功能代码可能令人沮丧．  \n  即使不完美，总是有东西在运行会使过程更愉快．  \n  这是关于体验小胜利，比如\"是的！现在它工作到这一点了！\"\n\n- **避免对特定框架，库或语言的偏见**  \n  虽然这本书专注于 Vue.js，但今天有无数优秀的框架，库和语言．  \n  事实上，除了 Vue.js 之外，我还有我的最爱，我经常从用它们构建的见解和服务中受益．  \n  这本书的目的纯粹是\"理解 Vue.js\"，不涉及对其他工具的排名或判断．\n\n## 本在线书籍的主题和结构\n\n由于这本书变得相当庞大，我设置了成就里程碑并将其分为不同的部分．\n\n- **最小示例部分**  \n   在这里，Vue.js 以最基本的形式实现．  \n   虽然这一部分涵盖了最小的功能集，但它将处理  \n   虚拟 DOM，响应式系统，编译器和 SFC（单文件组件）支持．  \n   然而，这些实现远非实用，并且高度简化．  \n   但是，对于那些想要 Vue.js 广泛概述的人来说，这一部分提供了足够的见解．  \n   作为介绍性部分，这里的解释比其他部分更详细．  \n   在本部分结束时，读者应该对阅读官方 Vue.js 源代码感到有些舒适．在功能上，您可以期望代码大致执行以下操作...\n\n  ```vue\n  <script>\n  import { reactive } from 'chibivue'\n\n  export default {\n    setup() {\n      const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n      const changeMessage = () => {\n        state.message += '!'\n      }\n\n      const handleInput = e => {\n        state.input = e.target?.value ?? ''\n      }\n\n      return { state, changeMessage, handleInput }\n    },\n  }\n  </script>\n\n  <template>\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\">click me!</button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n    </div>\n  </template>\n\n  <style>\n  .container {\n    height: 100vh;\n    padding: 16px;\n    background-color: #becdbe;\n    color: #2c3e50;\n  }\n  </style>\n  ```\n\n  ```ts\n  import { createApp } from 'chibivue'\n  import App from './App.vue'\n\n  const app = createApp(App)\n\n  app.mount('#app')\n  ```\n\n- **基础虚拟 DOM 部分**\n  在这一部分中，我们将为虚拟 DOM 实现相当实用的补丁渲染功能．虽然我们不会实现像 [Suspense](https://vuejs.org/guide/built-ins/suspense) 或其他优化等功能，但它将足够熟练地处理基本渲染任务．我们还将在这里实现调度器．\n\n- **基础响应式系统部分**\n  虽然我们在最小示例部分实现了 reactive API，但在这一部分中我们将实现其他 API．从 ref，watch 和 computed 等基本 API 开始，我们还将深入研究 effectScope 和 shallow 系列等更高级的 API．\n\n- **基础组件系统部分**\n  在这里，我们将承担与组件系统相关的基本实现．事实上，由于我们已经在基础虚拟 DOM 部分为组件系统设置了基础，这里我们将专注于组件系统的其他方面．这包括 props/emit，provide/inject，响应式系统的扩展和生命周期钩子等功能．\n\n- **基础模板编译器部分**\n  除了在基础虚拟 DOM 部分实现的虚拟 DOM 系统编译器之外，我们将实现 v-on，v-bind 和 v-for 等指令．通常，这将涉及组件的 template 选项，我们不会在这里涵盖 SFC（单文件组件）．\n\n- **基础 SFC 编译器部分**\n  在这里，我们将在利用基础模板编译器部分实现的模板编译器的同时，实现一个有些实用的 SFC 编译器．\n  具体来说，我们将实现 script setup 和编译器宏．\n  在这一点上，体验将非常接近使用常规 Vue．\n\n- **Web 应用程序要点部分**\n  当我们完成基础 SFC 编译器部分时，我们将拥有一套有些实用的 Vue.js 功能．然而，要开发 Web 应用程序，仍然缺少很多东西．例如，我们需要管理全局状态和路由器的工具．在这一部分中，我们将开发这样的外部插件，旨在从\"Web 应用程序开发\"的角度使我们的工具包更加实用．\n\n## 关于本书的意见和问题\n\n我打算尽我所能回应关于这本书的问题和反馈．请随时在 Twitter 上联系我（通过 DM 或直接在时间线上）．由于我已经公开了存储库，您也可以在那里发布问题．我知道我自己的理解并不完美，所以我感谢任何反馈．如果您发现任何解释不清楚或具有挑战性，请不要犹豫询问．我的目标是向尽可能多的人传播清晰正确的解释，我希望我们能够一起构建这个．\n\nhttps://x.com/ubugeeei\n\n## 关于 Discord 服务器\n\n我们为这本书创建了一个 Discord 服务器！（2024/01/01）\n~~在这里，我们分享公告，为与这本在线书籍相关的问题和技巧提供支持．~~ \\\n我们也欢迎随意对话，所以让我们与其他 chibivue 用户愉快地交流．\n目前，由于有很多日语使用者，大部分对话都是日语，但非日语使用者也欢迎毫不犹豫地加入！（完全可以使用您的母语）\n\n最近，我们不仅积极为 chibivue 做贡献，还作为 Vue.js 日本社区服务器的一部分！\n\n### 我们大致做什么\n\n- 自我介绍（可选）\n- 与 chibivue 相关的公告（如更新）\n- 分享技巧\n- 回答问题\n- 响应请求\n- 随意对话\n\n### 如何加入\n\n这是邀请链接: https://discord.gg/aVHvmbmSRy\n\n您也可以从这本书标题右上角的 Discord 按钮加入．\n\n## 关于作者\n\n**ubugeeei (もののけ王)**\n\n<img class=\"author-avatar\" src=\"/figures/_people/ubugeeei-avatar.jpg\" alt=\"ubugeeei\" width=\"160\" height=\"160\">\n\n[Vue.js](https://vuejs.org/about/team.html) Core Team 成员，[Vue.js Japan User Group](https://github.com/vuejs-jp) Core Staff，[Vite+](https://github.com/voidzero-dev/vite-plus) Core Contributor，[株式会社メイツ](https://github.com/mates-inc) Chief Engineer．\\\n[chibivue land](https://github.com/chibivue-land) King. https://chibivue.land\n\n我在围绕 Vue，语言处理器和开发体验制作工具与书籍，主要包括 [chibivue](https://github.com/chibivue-land/chibivue)，[Vize](https://github.com/ubugeeei/vize)，[Ox Content](https://github.com/ubugeeei/ox-content)，[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor) 和 [Vapor Moon](https://github.com/ubugeeei/vapor-moon)．\n\nhttps://wtrclred.io/\n\n如果您愿意，请作为赞助商支持我！ https://github.com/sponsors/ubugeeei\n\n## 赞助商\n\n<div class=\"sponsors-block\">\n<a class=\"sponsors-image-link\" href=\"https://github.com/sponsors/ubugeeei\">\n  <img class=\"sponsors-image sponsors-image--light\" src=\"/figures/_sponsors/ubugeeei-sponsors.png\" alt=\"ubugeeei's sponsors\" />\n  <img class=\"sponsors-image sponsors-image--dark\" src=\"/figures/_sponsors/ubugeeei-sponsors-dark.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n<p>如果您想支持我的工作，我将非常感激！</p>\n<p><a href=\"https://github.com/sponsors/ubugeeei\">https://github.com/sponsors/ubugeeei</a></p>\n\n</div>\n"
  },
  {
    "path": "book/online-book/src/zh-cn/00-introduction/020-what-is-vue.md",
    "content": "# 什么是 Vue.js？\n\n## 关于 Vue.js 的快速回顾\n\n让我们直奔主题．  \n但在此之前，让我们快速回顾一下 Vue.js 的全部内容．\n\n## Vue.js 到底是什么？\n\nVue.js 是一个\"友好，高性能，多功能的构建 Web 用户界面的框架\"．  \n这是官方文档主页上的说明．  \n对此，我认为直接引用官方的话而不添加我自己的解释会更清楚，所以我在下面引用了它们：\n\n> Vue（发音为 /vjuː/，类似 view）是一个用于构建用户界面的 JavaScript 框架。它建立在标准 HTML、CSS 和 JavaScript 的基础上，并提供了一套声明式的、组件化的编程模型，帮助你高效地开发用户界面，无论是简单还是复杂的。\n\n> 声明式渲染：Vue 基于标准 HTML 拓展了一套模板语法，使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。\n\n> 响应性：Vue 会自动跟踪 JavaScript 状态变化并在改变发生时响应式地更新 DOM。\n\n> 这里是一个最小的示例：\n>\n> ```ts\n> import { createApp } from 'vue'\n>\n> createApp({\n>   data() {\n>     return {\n>       count: 0,\n>     }\n>   },\n> }).mount('#app')\n> ```\n>\n> ```html\n> <div id=\"app\">\n>   <button @click=\"count++\">Count is: {{ count }}</button>\n> </div>\n> ```\n\n[参考来源](https://vuejs.org/guide/introduction.html#what-is-vue)\n\n对于声明式渲染和响应性，我们将在各自的章节中详细深入探讨，所以现在有一个高层次的理解就足够了．\n\n![Vue.js as an implementation map](/figures/00-introduction/what-is-vue/vue-implementation-map.svg)\n\n另外，这里出现了\"框架\"这个术语，Vue.js 将自己推广为\"渐进式框架\"．关于这一点，我认为最好直接参考文档的以下部分：\n\nhttps://vuejs.org/guide/introduction.html#the-progressive-framework\n\n## 官方文档和本书的区别\n\n官方文档专注于\"如何使用 Vue.js\"，提供了大量的教程和指南．\n\n然而，这本书采用了稍微不同的方法，专注于\"Vue.js 是如何实现的\"．我们将编写实际代码来创建一个迷你版本的 Vue.js．\n\n另外，这本书不是官方出版物，可能不够详尽．可能会有一些错误或遗漏，所以我很感谢任何反馈或更正．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/00-introduction/030-vue-core-components.md",
    "content": "# 构成 Vue.js 的关键要素\n\n## Vue.js 仓库\n\nVue.js 可以在这个仓库中找到：  \nhttps://github.com/vuejs/core\n\n有趣的是，这是 v3 的仓库．对于 v2 和更早的版本，您可以在另一个仓库中找到：  \nhttps://github.com/vuejs/vue\n\n为了本次讨论，我们将专注于核心仓库（v3）．\n\n## 构成 Vue.js 的主要要素\n\n让我们首先对 Vue.js 的实现有一个整体的理解．\\\nVue.js 仓库中有一个关于贡献的 markdown 文件；  \n如果您感兴趣，可以查看它以了解其架构．（不过，跳过也没关系．）\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md\n\n从大的方面来说，Vue.js 包含以下主要组件：\n\n## 运行时（Runtime）\n\n运行时包含影响实际操作的所有内容 - 从渲染到组件状态管理．\\\n这指的是在浏览器或服务器（在 SSR 的情况下）上运行的用 Vue.js 开发的 Web 应用程序的全部内容．具体包括：\n\n### 组件系统\n\nVue.js 是一个面向组件的框架．根据用户的需求，您可以可维护地创建和封装组件以供重用．\\\n它还提供组件之间状态共享（props/emits 或 provide/inject）和生命周期钩子等功能．\n\n### 响应式系统\n\n它跟踪组件持有的状态，并在发生变化时更新屏幕．\\\n这种监控和响应机制称为响应式．\n\n```ts\nimport { ref } from 'vue'\n\nconst count = ref(0)\n\n// 当执行这个函数时，显示计数的屏幕也会更新\nconst increment = () => {\n  count.value++\n}\n```\n\n（仅仅通过改变一个值就能更新屏幕，这很神奇，对吧？）\n\n### 虚拟 DOM 系统\n\n虚拟 DOM 系统是 Vue.js 的另一个强大机制．它定义了一个模仿 DOM 的 JavaScript 对象在 JS 运行时中．\\\n更新时，它将当前的虚拟 DOM 与新的虚拟 DOM 进行比较，并仅将差异反映到真实 DOM 中．\\\n我们将在专门的章节中深入探讨这一点．\n\n## 编译器（Compiler）\n\n编译器负责将开发者接口转换为内部实现．\\\n\"开发者接口\"是指\"使用 Vue.js 进行 Web 应用程序开发的开发者\"和\"Vue 内部操作\"之间的边界．\\\n本质上，当您使用 Vue.js 编写时，有些部分显然不是纯 JavaScript - 比如模板指令或单文件组件．\\\nVue.js 提供这些语法并将它们转换为纯 JavaScript．\\\n此功能仅在开发阶段使用，不是实际运行的 Web 应用程序的一部分．\\\n（它仅仅编译为 JavaScript 代码）．\n\n编译器有两个主要部分：\n\n### 模板编译器\n\n顾名思义，这是模板部分的编译器．\\\n具体来说，它处理 v-if 或 v-on 等指令，用户组件标记（如 <Counter />）以及插槽等功能．\n\n### SFC 编译器\n\n正如您可能猜到的，这代表单文件组件编译器．\\\n它允许您在单个 .vue 文件中定义组件的模板，脚本和样式．\\\n在 script setup 中使用的函数，如 [defineProps 或 defineEmits](https://cn.vuejs.org/api/sfc-script-setup#defineprops-defineemits) 也由此编译器提供．\\\n这个 SFC 编译器通常与 Webpack 或 Vite 等工具结合使用．\\\n作为其他工具插件的实现不在核心仓库中．\\\nSFC 编译器的主要功能在核心中，但插件在不同的仓库中实现．\\\n（参考：[vitejs/vite-plugin-vue](https://github.com/vitejs/vite-plugin-vue)）\n\n顺便说一下，我们将实现一个实际的 Vite 插件来操作我们的自定义 SFC 编译器．\n\n## 窥探 vuejs/core 目录\n\n现在我们对 Vue 的主要要素有了大致的了解，让我们看看实际的源代码是什么样子的（尽管我们只是在讨论目录）．\\\n主要源代码存储在\"packages\"目录中．\n\nhttps://github.com/vuejs/core/tree/main/packages\n\n一些需要关注的关键目录是：\n\n- compiler-core\n- compiler-dom\n- compiler-sfc\n- reactivity\n- runtime-core\n- runtime-dom\n- vue\n\n为了理解它们的相互依赖关系，贡献指南中的图表特别有见地．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n<br/>\n在这本书中，我们将为所有这些主题提供实现和解释。\n"
  },
  {
    "path": "book/online-book/src/zh-cn/00-introduction/040-setup-project.md",
    "content": "# 如何进行本书学习和环境设置\n\n## Web Playground\n\n本书提供了一个 **Web Playground**，您可以直接在浏览器中试用每个章节的实现代码．\n无需任何环境设置，您可以立即编辑和运行代码，所以请先在这里体验 chibivue 的实际运行！\n\n### 如何启动 Playground\n\n```sh\n$ git clone https://github.com/chibivue-land/chibivue\n$ cd chibivue\n$ pnpm install\n$ pnpm play\n```\n\n在浏览器中访问显示的 URL（例如：`http://localhost:5173/`）即可启动 Playground．\n\n### Playground 布局\n\n![Initial Web Playground screen](/figures/00-introduction/setup-project/web-playground-initial.png)\n\nPlayground 由四个区域组成：\n\n| 区域 | 说明 |\n|------|------|\n| **Explorer（左侧）** | 显示项目文件树．点击文件可在编辑器中打开 |\n| **Editor（中央）** | 使用 Monaco Editor 编辑代码 |\n| **Preview（右侧）** | 显示在 WebContainer 上运行的开发服务器预览 |\n| **Terminal / Console（底部）** | 查看终端输出和 console.log 内容 |\n\n### 使用方法\n\n1. **选择章节**\n   从屏幕顶部的下拉菜单中选择您想学习的章节．\n   您也可以使用搜索框过滤章节名称．\n\n2. **点击 Run**\n   点击「Run」按钮启动 WebContainer，安装依赖并启动开发服务器．\n   首次运行需要一些时间，稍等片刻后，结果将显示在 Preview 区域．\n\n3. **编辑代码**\n   在编辑器中编辑代码，然后点击「Apply」按钮应用更改．\n   通过 HMR（热模块替换），更改会实时反映．\n\n4. **查看控制台**\n   点击「Console」标签页查看 console.log 等输出内容．\n\n![Web Playground console output](/figures/00-introduction/setup-project/web-playground-console.png)\n\n::: tip\nWeb Playground 使用 [WebContainer](https://webcontainers.io/)．\n在某些浏览器或环境中可能无法运行．在这种情况下，请参考下面的本地环境设置．\n:::\n\n## 如何进行本书学习\n\n我们将立即开始 Vue.js 的简单实现．以下是一些需要记住的要点，注意事项和其他重要信息：\n\n- 项目名称将是\"chibivue\"．我们将把本书中涵盖的基本 Vue.js 实现称为\"chibivue\"．\n- 如最初提到的，我们的主要方法将是\"重复小型开发\"．\n- 每个阶段的源代码都包含在本书的附录中，可以在 https://github.com/chibivue-land/chibivue/tree/main/book/impls 找到．我们不会在书中为所有源代码提供详细解释，所以请根据需要参考附录．\n- 最终代码依赖于几个包．DIY 内容的一个常见问题是关于\"应该手工实现多少才能称之为自制\"的争论．虽然我们不会在本书中手工编写所有源代码，但我们将积极使用与 Vue.js 官方代码中使用的类似的包．例如，我们将使用 [Babel](https://babeljs.io/)．请放心，我们的目标是使这本书尽可能对初学者友好，为必要的包提供最少的解释．\n\n## 环境设置\n\n现在，让我们快速进入环境设置！\\\n我将列出我们将使用的工具和版本：\n\n- 运行时：[Node.js](https://nodejs.org/en) v24\n- 语言：[TypeScript](https://www.typescriptlang.org/)\n- 包管理器：[pnpm](https://pnpm.io/) v10\n- 构建工具：[Vite](https://vite.dev/) v8\n\n## 安装 Node.js\n\n你们大多数人可能都熟悉这一步．请自行设置．我们将跳过这里的详细解释．\n\n## 安装 pnpm\n\n你们中的许多人可能通常使用 npm 或 yarn．对于这本书，我们将使用 pnpm，所以请也安装它．命令大多与 npm 相似．\nhttps://pnpm.io/installation\n\n\n## 创建项目\n\n::: details 急于开始的快速启动...\n\n虽然我将解释手动创建项目的步骤，但实际上有一个为设置准备的工具．  \n如果您觉得手动过程繁琐，请随时使用这个工具！\n\n1. 克隆 chibivue．\n\n   ```sh\n   $ git clone https://github.com/chibivue-land/chibivue\n   ```\n\n2. 执行脚本．  \n   输入您想要设置的目录路径．\n\n   ```sh\n   $ cd chibivue\n   $ pnpm setup:book ../my-chibivue-project\n   ```\n\n:::\n\n在您选择的任何目录中创建项目．为了方便起见，我们将项目的根路径表示为 `~`（例如，`~/src/main.ts`）．\n\n这次，我们将把主要的\"chibivue\"与测试其功能的游乐场分开．游乐场将简单地调用\"chibivue\"并用 Vite 打包它．我们预期这样的结构．\n\n```\n~\n|- examples\n|    |- playground\n|\n|- packages\n|- tsconfig.js\n```\n\n我们将在名为\"examples\"的目录中实现游乐场．\n我们将在\"packages\"中实现 chibivue 的核心 TypeScript 文件，并从示例端导入它们．\n\n以下是构建它的步骤．\n\n### 构建主项目\n\n```sh\n## 请创建一个专门用于 chibivue 的目录并导航到其中。（此后将省略此类注释。）\npwd # ~/\npnpm init\npnpm add -D @types/node\nmkdir packages\ntouch packages/index.ts\ntouch tsconfig.json\n```\n\ntsconfig.json 的内容\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\npackages/index.ts 的内容\n\n```ts\nconsole.log(\"Hello, World\")\n```\n\n### 构建游乐场端\n\n```sh\npwd # ~/\nmkdir examples\ncd examples\npnpm dlx create-vite\n\n## --------- 使用 Vite CLI 设置\n## Project name: playground\n## Select a framework: Vanilla\n## Select a variant: TypeScript\n```\n\n从用 Vite 创建的项目中删除不必要的项目．\n\n```sh\npwd # ~/examples/playground\nrm -rf public\nrm -rf src # 我们将重新创建它，因为有不必要的文件。\nmkdir src\ntouch src/main.ts\n```\n\nsrc/main.ts 的内容\n\n※ 现在，\"from\"后面会有错误，但我们将在接下来的步骤中解决这个问题，所以没有问题．\n\n```ts\nimport \"chibivue\"\n```\n\n按如下方式修改 index.html．\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n在 Vite 项目中配置别名，以便能够导入您在 chibivue 中实现的内容．\n\n```sh\npwd # ~/examples/playground\ntouch vite.config.js\n```\n\nvite.config.js 的内容\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n})\n```\n\n按如下方式修改 tsconfig.json．\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Node\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n```\n\n最后，让我们在 chibivue 项目的 package.json 中添加一个命令来启动游乐场并尝试启动它！\n\n将以下内容附加到 ~/package.json\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  }\n}\n```\n\n```sh\npwd # ~\npnpm dev\n```\n\n访问使用此命令启动的开发服务器．如果显示消息，则设置完成．\n\n![Hello chibivue rendered in the browser](/figures/00-introduction/setup-project/hello-chibivue-result.png)\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls//00_introduction/010_project_setup)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/010-create-app-api.md",
    "content": "# 第一次渲染和 createApp API\n\n## 从哪里开始？\n\n现在，让我们开始逐步实现 chibivue．我们应该如何进行实现？\n\n<KawaikoNote variant=\"question\" title=\"开始实现！\">\n\n这是深入 Vue.js 内部实现的激动人心的时刻！\n\"从哪里开始\"实际上是一个重要的要点．\n\n</KawaikoNote>\n\n这是作者在创建新东西时总是牢记的一点：首先，思考软件将如何被使用．\\\n为了方便起见，让我们称之为\"开发者接口\"．\n\n这里，\"开发者\"指的是使用 chibivue 开发 Web 应用程序的人，而不是 chibivue 本身的开发者．\\\n换句话说，在开发 chibivue 时，让我们参考原始 Vue.js 的开发者接口作为参考．\\\n具体来说，让我们看看在使用 Vue.js 开发 Web 应用程序时要写什么．\n\n## 开发者接口层级？\n\n我们在这里需要注意的是，Vue.js 有多个开发者接口，每个接口都有不同的层级．这里，层级指的是它与原始 JavaScript 的接近程度．\\\n例如，以下是使用 Vue 显示 HTML 的开发者接口示例：\n\n1. 在单文件组件中编写模板\n\n```vue\n<!-- App.vue -->\n<template>\n  <div>Hello world.</div>\n</template>\n```\n\n```ts\nimport { createApp } from 'vue'\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\n2. 使用 template 选项\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  template: '<div>Hello world.</div>',\n})\n\napp.mount('#app')\n```\n\n3. 使用 render 选项和 h 函数\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\n还有其他选项，但让我们考虑这三个开发者接口．\\\n哪一个最接近原始 JavaScript？答案是\"使用 render 选项和 h 函数\"（选项 3）．\\\n选项 1 需要实现 SFC 编译器和打包器（或加载器），选项 2 需要编译传递给模板的 HTML（将其转换为 JavaScript 代码）才能工作．\n\n为了方便起见，让我们称更接近原始 JS 的开发者接口为\"低级开发者接口\"．\\\n这里重要的是\"从低级部分开始实现\"．\\\n原因是在许多情况下，高级描述被转换为低级描述并执行．\\\n换句话说，选项 1 和 2 最终都在内部转换为选项 3 的形式．\\\n这种转换的实现称为\"编译器\"．\n\n<KawaikoNote variant=\"funny\" title=\"从低级开始！\">\n\n\"低级\"听起来可能很弱，但实际上恰恰相反！\n这是基础，没有坚实的基础，你无法构建更高级的功能．\n\n</KawaikoNote>\n\n所以，让我们从实现像选项 3 这样的开发者接口开始！\n\n## createApp API 和渲染\n\n虽然我们的目标是选项 3 的形式，但我们仍然不太了解 h 函数，而且由于这本书的目标是增量开发，让我们不要立即瞄准选项 3 的形式．\\\n相反，让我们从实现一个返回要显示的消息的简单渲染函数开始．\n\n图像 ↓\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n## 立即实现\n\n让我们在 `~/packages/index.ts` 中创建 createApp 函数．\\\n注意：由于不需要输出\"Hello, World\"，我们将删除它．\n\n```ts\nexport type Options = {\n  render: () => string\n}\n\nexport type App = {\n  mount: (selector: string) => void\n}\n\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render()\n      }\n    },\n  }\n}\n```\n\n这非常简单．让我们在游乐场中试试．\n\n`~/examples/playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n我们能够在屏幕上显示消息！做得好！\n\n![createApp example rendered in the browser](/figures/10-minimum-example/create-app-api/hello-create-app-result.png)\n\n<KawaikoNote variant=\"surprise\" title=\"第一步完成！\">\n\n只用了几十行代码，一个 Vue.js 风格的应用就运行起来了！\n这小小的一步是理解框架的一大步．\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/010_create_app)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/015-package-architecture.md",
    "content": "# 包架构\n\n## 重构\n\n您可能会想，\"嗯？我们只实现了这么一点，您就想重构？\"但这本书的目标之一是\"能够阅读 Vue.js 源代码\"．\n\n考虑到这一点，我希望始终关注 Vue.js 风格的文件和目录结构．所以，请允许我做一点重构...\n\n### Vue.js 设计\n\n#### runtime-core 和 runtime-dom\n\n让我稍微解释一下官方 Vue.js 的结构．在这次重构中，我们将创建两个目录：\"runtime-core\" 和 \"runtime-dom\"．\n\n<KawaikoNote variant=\"question\" title=\"为什么要分开？\">\n\n「代码已经能运行了，为什么还要拆分？」你可能会这样想．\\\n实际上 Vue.js 被设计成不仅可以在浏览器中运行，还可以在 SSR（服务端渲染）和原生应用（Vue Native）等环境中运行．\\\n这就是为什么要将「纯逻辑」和「DOM 操作」分离的原因！\n\n</KawaikoNote>\n\n为了解释它们各自是什么，\"runtime-core\" 包含 Vue.js 运行时的核心功能．在这个阶段，可能很难理解什么是核心，什么不是．\n\n所以，我认为通过查看与 \"runtime-dom\" 的关系会更容易理解．顾名思义，\"runtime-dom\" 是一个包含依赖于 DOM 的实现的目录．粗略地说，它可以理解为\"依赖于浏览器的操作\"．它包括 DOM 操作，如 querySelector 和 createElement．\n\n在 runtime-core 中，我们不编写这样的操作，而是设计它在纯 TypeScript 的世界中描述 Vue.js 运行时的核心逻辑．例如，它包括与虚拟 DOM 和组件相关的实现．嗯，我认为随着 chibivue 开发的进展，这会变得更清楚，所以如果您不理解，请暂时按照书中描述的进行重构．\n\n#### 每个文件的角色和依赖关系\n\n我们现在将在 runtime-core 和 runtime-dom 中创建一些文件．必要的文件如下：\n\n```sh\npwd # ~\nmkdir packages/runtime-core\nmkdir packages/runtime-dom\n\n## core\ntouch packages/runtime-core/index.ts\ntouch packages/runtime-core/apiCreateApp.ts\ntouch packages/runtime-core/component.ts\ntouch packages/runtime-core/componentOptions.ts\ntouch packages/runtime-core/renderer.ts\n\n## dom\ntouch packages/runtime-dom/index.ts\ntouch packages/runtime-dom/nodeOps.ts\n```\n\n至于这些文件的角色，仅仅用文字解释可能很难理解，所以请参考以下图表：\n\n![runtime-core and runtime-dom responsibilities](/figures/10-minimum-example/package-architecture/runtime-core-dom-overview.svg)\n\n#### 渲染器的设计\n\n如前所述，Vue.js 将依赖于 DOM 的部分与 Vue.js 的纯核心功能分离．首先，我希望您注意 \"runtime-core\" 中的渲染器工厂和 \"runtime-dom\" 中的 nodeOps．在我们之前实现的示例中，我们直接在 createApp 返回的应用程序的 mount 方法中进行渲染．\n\n```ts\n// 这是之前的代码\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render() // 渲染\n      }\n    },\n  }\n}\n```\n\n此时，代码很短，一点也不复杂，所以乍一看似乎很好．然而，当我们将来为虚拟 DOM 编写补丁渲染逻辑时，它会变得更加复杂．在 Vue.js 中，这个负责渲染的部分被分离为\"渲染器\"．那就是 \"runtime-core/renderer.ts\"．说到渲染，很容易想象它依赖于在 SPA 中控制浏览器 DOM 的 API（document）（创建元素，设置文本等）．因此，为了将这个依赖于 DOM 的部分与 Vue.js 的核心渲染逻辑分离，已经做了一些技巧．它是这样工作的：\n\n- 在 `runtime-dom/nodeOps` 中实现一个用于 DOM 操作的对象．\n- 在 `runtime-core/renderer` 中实现一个工厂函数，该函数生成一个只包含渲染逻辑的对象．在这样做时，确保将处理节点（不限于 DOM）的对象作为参数传递给工厂函数．\n- 在 `runtime-dom/index.ts` 中使用 `nodeOps` 和 `renderer` 的工厂来完成渲染器．\n\n这是图表中用红色突出显示的部分．\n![Renderer dependency injection](/figures/10-minimum-example/package-architecture/renderer-dependency-injection.svg)\n\n让我解释一下源代码．此时，虚拟 DOM 的渲染功能尚未实现，所以我们将创建与之前相同功能的代码．\n\n首先，在 `runtime-core/renderer` 中实现用于节点（不限于 DOM）操作的对象接口．\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  setElementText(node: HostNode, text: string): void\n}\n\nexport interface RendererNode {\n  [key: string]: any\n}\n\nexport interface RendererElement extends RendererNode {}\n```\n\n目前，只有 `setElementText` 函数，但您可以想象将来会实现 `createElement` 和 `removeChild` 等函数．\n\n关于 `RendererNode` 和 `RendererElement`，请暂时忽略它们．（这里的实现只是为成为节点的对象定义一个通用类型，而不依赖于 DOM．）  \n在此文件中实现渲染器工厂函数，该函数将 `RendererOptions` 作为参数．\n\n```ts\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  message: string,\n  container: HostElement,\n) => void\n\nexport function createRenderer(options: RendererOptions) {\n  const { setElementText: hostSetElementText } = options\n\n  const render: RootRenderFunction = (message, container) => {\n    hostSetElementText(container, message) // 在这种情况下，我们只是简单地插入消息，所以实现是这样的\n  }\n\n  return { render }\n}\n```\n\n接下来，在 `runtime-dom/nodeOps` 中实现 `nodeOps`．\n\n```ts\nimport { RendererOptions } from '../runtime-core'\n\nexport const nodeOps: RendererOptions<Node> = {\n  setElementText(node, text) {\n    node.textContent = text\n  },\n}\n```\n\n这里没有什么特别困难的．\n\n现在，让我们在 `runtime-dom/index.ts` 中完成渲染器．\n\n```ts\nimport { createRenderer } from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\n```\n\n这样，渲染器的重构就完成了．\n\n#### DI 和 DIP\n\n让我们看看渲染器的设计．总结一下：\n\n- 在 `runtime-core/renderer` 中实现一个工厂函数来生成渲染器．\n- 在 `runtime-dom/nodeOps` 中实现一个用于依赖于 DOM 的操作（操纵）的对象．\n- 在 `runtime-dom/index` 中结合工厂函数和 `nodeOps` 来生成渲染器．\n\n这些是\"DIP\"和\"DI\"的概念．\n\n<KawaikoNote variant=\"warning\" title=\"这有点难\">\n\nDI 和 DIP 是设计模式中比较难理解的概念．\\\n一开始只需要有个大概的印象就可以了！\\\n随着你写更多的代码，你会恍然大悟：「啊，原来是这样！」\n\n</KawaikoNote>\n\n首先，让我们谈谈 DIP（依赖倒置原则）．通过实现接口，我们可以倒置依赖关系．您应该注意的是在 `renderer.ts` 中实现的 `RendererOptions` 接口．工厂函数和 `nodeOps` 都应该遵守这个 `RendererOptions` 接口（依赖于 `RendererOptions` 接口）．\n\n<KawaikoNote variant=\"funny\" title=\"用做菜来比喻\">\n\n如果把 DIP 比作做菜的话...\\\n只要有「食谱（interface）」，无论食材是国产还是进口，都能做出同样的菜．\\\n渲染器（厨师）只需要按照「RendererOptions（食谱）」来做，不需要关心实际的食材（DOM 操作或其他操作）．\n\n</KawaikoNote>\n\n通过使用这个，我们执行 DI．依赖注入（DI）是一种通过从外部注入对象所依赖的对象来减少依赖的技术．在这种情况下，渲染器依赖于实现 `RendererOptions` 的对象（在这种情况下是 `nodeOps`）．我们不是直接从渲染器实现这种依赖，而是将其作为工厂的参数接收．通过使用这些技术，我们确保渲染器不依赖于 DOM．\n\n<KawaikoNote variant=\"base\" title=\"总结一下\">\n\n**DIP**: 依赖接口（契约），而不是具体实现\\\n**DI**: 从外部接收依赖（注入它们）\n\n结合这两者，可以让代码更灵活，更易于测试！\n\n</KawaikoNote>\n\n如果您不熟悉 DI 和 DIP，它们可能是困难的概念，但它们是经常使用的重要技术，所以我希望您能够自己研究和理解它们．\n\n### 完成 createApp\n\n现在，让我们回到实现．现在渲染器已经生成，我们需要做的就是考虑以下图表中的红色区域．\n\n![createAppAPI factory flow](/figures/10-minimum-example/package-architecture/create-app-api-factory.svg)\n\n然而，这是一个简单的任务．我们只需要实现 createApp 的工厂函数，以便它可以接受我们之前创建的渲染器．\n\n```ts\n// ~/packages/runtime-core apiCreateApp.ts\n\nimport { Component } from './component'\nimport { RootRenderFunction } from './renderer'\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void\n}\n\nexport type CreateAppFunction<HostElement> = (\n  rootComponent: Component,\n) => App<HostElement>\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const message = rootComponent.render!()\n        render(message, rootContainer)\n      },\n    }\n\n    return app\n  }\n}\n```\n\n```ts\n// ~/packages/runtime-dom/index.ts\n\nimport {\n  CreateAppFunction,\n  createAppAPI,\n  createRenderer,\n} from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\nconst _createApp = createAppAPI(render)\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args)\n  const { mount } = app\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector)\n    if (!container) return\n    mount(container)\n  }\n\n  return app\n}) as CreateAppFunction<Element>\n```\n\n我将类型移动到了 `~/packages/runtime-core/component.ts`，但这并不重要，所以请参考源代码（这只是与原始 Vue.js 对齐）．\n\n现在我们更接近原始 Vue.js 的源代码，让我们测试一下．如果消息仍然显示，那就没问题．\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/015_package_architecture)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/020-simple-h-function.md",
    "content": "# 让我们启用 HTML 元素的渲染\n\n## 什么是 h 函数？\n\n<KawaikoNote variant=\"question\" title=\"'h' 代表什么？\">\n\n`h` 是 `hyperscript` 的缩写．因为它是一个用 JavaScript 表达\nHTML（Hyper Text Markup Language）的函数，所以得名！\n\n</KawaikoNote>\n\n到目前为止，我们已经让以下源代码工作：\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n这是一个简单地在屏幕上渲染\"Hello World.\"的函数．  \n由于只有一条消息有点孤单，让我们考虑一个也可以渲染 HTML 元素的开发者接口．  \n这就是 `h 函数` 的用武之地．这个 `h` 代表 `hyperscript`，作为在 JavaScript 中编写 HTML（超文本标记语言）的函数提供．\n\n> h() 是 hyperscript 的缩写 - 意思是\"产生 HTML（超文本标记语言）的 JavaScript\"。这个名称继承自许多虚拟 DOM 实现共享的约定。一个更具描述性的名称可能是 createVnode()，但当您必须在渲染函数中多次调用此函数时，较短的名称会有所帮助。\n\n引用：https://vuejs.org/guide/extras/render-function.html#creating-vnodes\n\n让我们看看 Vue.js 中的 h 函数．\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['HelloWorld']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n作为 h 函数的基本用法，您将标签名称指定为第一个参数，将属性指定为第二个参数，将子元素数组指定为第三个参数．  \n在这里，我特别提到了\"基本用法\"，因为 h 函数实际上对其参数有多种语法，您可以省略第二个参数或不对子元素使用数组．  \n但是，在这里我们将以最基本的语法实现它．\n\n## 我们应该如何实现它？\n\n现在我们了解了开发者接口，让我们决定如何实现它．  \n需要注意的重要一点是它如何用作渲染函数的返回值．  \n这意味着 `h` 函数返回某种对象并在内部使用该结果．\\\n由于复杂的子元素很难理解，让我们考虑实现简单 h 函数的结果．\n\n```ts\nconst result = h('div', { class: 'container' }, ['hello'])\n```\n\n`result` 中应该存储什么样的结果？（我们应该如何格式化结果以及如何渲染它？）\n\n让我们假设以下对象存储在 `result` 中：\n\n```ts\nconst result = {\n  type: 'div',\n  props: { class: 'container' },\n  children: ['hello'],\n}\n```\n\n换句话说，我们将从渲染函数接收类似于上面的对象，并使用它来执行 DOM 操作并渲染它．\\\n图像是这样的（在 `createApp` 的 `mount` 内部）：\n\n```ts\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const node = rootComponent.render!()\n    render(node, rootContainer)\n  },\n}\n```\n\n嗯，唯一改变的是我们将 `message` 字符串更改为 `node` 对象．  \n我们现在要做的就是在渲染函数中基于对象执行 DOM 操作．\n\n实际上，这个对象有一个名字，\"虚拟 DOM\"．  \n我们将在虚拟 DOM 章节中更多地解释虚拟 DOM，所以现在只需记住这个名字．\\\n\n## 实现 h 函数\n\n首先，创建必要的文件．\n\n```sh\npwd # ~\ntouch packages/runtime-core/vnode.ts\ntouch packages/runtime-core/h.ts\n```\n\n在 vnode.ts 中定义类型．这就是我们在 vnode.ts 中要做的全部．\n\n```ts\nexport interface VNode {\n  type: string\n  props: VNodeProps\n  children: (VNode | string)[]\n}\n\nexport interface VNodeProps {\n  [key: string]: any\n}\n```\n\n接下来，在 h.ts 中实现函数体．\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return { type, props, children }\n}\n```\n\n现在，让我们尝试在游乐场中使用 h 函数．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\n屏幕上的显示被破坏了，但如果您在 apiCreateApp 中添加日志，您可以看到它按预期工作．\n\n```ts\nmount(rootContainer: HostElement) {\n  const vnode = rootComponent.render!();\n  console.log(vnode); // 检查日志\n  render(vnode, rootContainer);\n},\n```\n\n现在，让我们实现渲染函数．\\\n在 RendererOptions 中实现 `createElement`，`createText` 和 `insert`．\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  createElement(type: string): HostNode // 添加\n\n  createText(text: string): HostNode // 添加\n\n  setElementText(node: HostNode, text: string): void\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void // 添加\n}\n```\n\n在渲染函数中实现 `renderVNode` 函数．现在，我们忽略 `props`．\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  const {\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options\n\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === 'string') return hostCreateText(vnode)\n    const el = hostCreateElement(vnode.type)\n\n    for (const child of vnode.children) {\n      const childEl = renderVNode(child)\n      hostInsert(childEl, el)\n    }\n\n    return el\n  }\n\n  const render: RootRenderFunction = (vnode, container) => {\n    const el = renderVNode(vnode)\n    hostInsert(el, container)\n  }\n\n  return { render }\n}\n```\n\n在 runtime-dom 的 nodeOps 中，定义实际的 DOM 操作．\n\n```ts\nexport const nodeOps: RendererOptions<Node> = {\n  // 添加\n  createElement: tagName => {\n    return document.createElement(tagName)\n  },\n\n  // 添加\n  createText: (text: string) => {\n    return document.createTextNode(text)\n  },\n\n  setElementText(node, text) {\n    node.textContent = text\n  },\n\n  // 添加\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\n嗯，此时，您应该能够在屏幕上渲染元素．\\\n尝试在游乐场中编写和测试各种东西！\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n耶！现在我们可以使用 h 函数来渲染各种标签！\n\n![VNode log from a simple h function](/figures/10-minimum-example/simple-h-function/basic-vnode-log.png)\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/025-event-handler-and-attrs.md",
    "content": "# 让我们支持事件处理器和属性\n\n## 仅仅显示有点孤单\n\n既然有机会，让我们实现 props，这样我们就可以使用点击事件和样式．\n\n关于这部分，虽然直接在 renderVNode 中实现也可以，但让我们尝试在考虑遵循原始设计的同时进行．\n\n请注意原始 Vue.js 的 runtime-dom 目录．\n\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src\n\n我希望您特别注意 `modules` 目录和 `patchProp.ts` 文件．\n\n在 modules 目录内，有用于操作类，样式和其他 props 的文件．\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src/modules\n\n这些都在 patchProp.ts 中组合成一个名为 patchProp 的函数，并混合到 nodeOps 中．\n\n与其用文字解释，我将尝试基于这种设计来做．\n\n## 创建 patchProps 的框架\n\n首先，让我们创建框架．\n\n```sh\npwd # ~\ntouch packages/runtime-dom/patchProp.ts\n```\n\n`runtime-dom/patchProp.ts` 的内容\n\n```ts\ntype DOMRendererOptions = RendererOptions<Node, Element>\n\nconst onRE = /^on[^a-z]/\nexport const isOn = (key: string) => onRE.test(key)\n\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    // patchEvent(el, key, value); // 我们稍后会实现这个\n  } else {\n    // patchAttr(el, key, value); // 我们稍后会实现这个\n  }\n}\n```\n\n由于 patchProp 的类型在 RendererOptions 中没有定义，让我们定义它．\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement\n> {\n  // 添加\n  patchProp(el: HostElement, key: string, value: any): void;\n  .\n  .\n  .\n```\n\n这样，我们需要修改 nodeOps 以排除 patchProps 以外的部分．\n\n```ts\n// 省略 patchProp\nexport const nodeOps: Omit<RendererOptions, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n  .\n  .\n  .\n```\n\n然后，在 `runtime-dom/index` 中生成渲染器时，让我们更改为一起传递 patchProp．\n\n```ts\nconst { render } = createRenderer({ ...nodeOps, patchProp })\n```\n\n## 事件处理器\n\n让我们实现 patchEvent．\n\n```sh\npwd # ~\nmkdir packages/runtime-dom/modules\ntouch packages/runtime-dom/modules/events.ts\n```\n\n实现 events.ts．\n\n```ts\ninterface Invoker extends EventListener {\n  value: EventValue\n}\n\ntype EventValue = Function\n\nexport function addEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.addEventListener(event, handler)\n}\n\nexport function removeEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.removeEventListener(event, handler)\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {})\n  const existingInvoker = invokers[rawName]\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value\n  } else {\n    const name = parseName(rawName)\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value))\n      addEventListener(el, name, invoker)\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker)\n      invokers[rawName] = undefined\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase()\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e)\n  }\n  invoker.value = initialValue\n  return invoker\n}\n```\n\n这有点长，但如果您拆分它，这是一个非常简单的代码．\n\naddEventListener 顾名思义，只是一个用于注册事件监听器的函数．\\\n虽然实际上需要在适当的时机删除它，但我们现在将忽略它．\n\n在 patchEvent 中，我们用一个名为 invoker 的函数包装监听器并注册监听器．\\\n关于 parseName，它只是通过删除\"on\"将 prop 键名（如 `onClick` 和 `onInput`）转换为小写（例如 click，input）．\n需要注意的一点是，为了不向同一元素添加重复的 addEventListeners，我们将 invoker 添加到名为 `_vei`（vue event invokers）的元素中．\\\n通过在补丁时更新 existingInvoker.value，我们可以在不添加重复 addEventListeners 的情况下更新处理器．\\\n术语\"invoker\"简单地意味着\"执行者\"．没有更深的含义；它只是一个存储将实际执行的处理器的对象．\n\n现在让我们将其合并到 patchProps 中，并尝试在 renderVNode 中使用它．\n\npatchProps\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value)\n  } else {\n    // patchAttr(el, key, value); // 我们稍后会实现这个\n  }\n}\n```\n\nruntime-core/renderer.ts 中的 renderVNode\n\n```ts\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options;\n  .\n  .\n  .\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === \"string\") return hostCreateText(vnode);\n    const el = hostCreateElement(vnode.type);\n\n    // 这里\n    Object.entries(vnode.props).forEach(([key, value]) => {\n      hostPatchProp(el, key, value);\n    });\n    .\n    .\n    .\n```\n\n现在让我们在游乐场中运行它．我将尝试显示一个简单的警报．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n我们现在可以使用 h 函数注册事件处理器！\n\n![Event handler example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/event-handler-result.png)\n\n## 尝试支持其他 props\n\n在此之后，只需对 setAttribute 做同样的事情．\\\n我们将在 `modules/attrs.ts` 中实现这个．\\\n我希望您自己尝试．答案将在本章末尾的源代码中附上，所以请在那里检查．\\\n一旦您可以使这段代码工作，您就达到了目标．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', { id: 'my-app' }, [\n      h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n![Attribute patching example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/attrs-result.png)\n\n现在我们可以处理广泛的 HTML！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system.md",
    "content": "# 响应式系统的先决知识\n\n## 这次我们的目标开发者接口\n\n从这里开始，我们将讨论 Vue.js 的精髓，即响应式系统．\n\n<KawaikoNote variant=\"surprise\" title=\"重头戏来了！\">\n\n这是 Vue.js 的核心！\\\n一旦理解了响应式系统，你就会明白 Vue.js 的「魔法」是如何实现的．\\\n虽然有点难，但让我们一起努力吧！\n\n</KawaikoNote>\n\n之前的实现虽然看起来类似于 Vue.js，但在功能上实际上并不是 Vue.js．  \n我只是实现了初始的开发者接口，并使其能够显示各种 HTML．\n\n然而，就目前而言，一旦屏幕被渲染，它就保持不变，作为一个 Web 应用程序，它变成了一个静态站点．  \n从现在开始，我们将添加状态来创建更丰富的 UI，并在状态改变时更新渲染．\n\n首先，让我们像往常一样思考它将是什么样的开发者接口．  \n这样如何？\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n\n    const increment = () => {\n      state.count++\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n如果您习惯于使用单文件组件（SFC）进行开发，这可能看起来有点不熟悉．  \n这是一个使用 `setup` 选项来保存状态并返回渲染函数的开发者接口．  \n实际上，Vue.js 有这样的表示法．\n\nhttps://vuejs.org/api/composition-api-setup.html#usage-with-render-functions\n\n我们用 `reactive` 函数定义状态，实现一个名为 `increment` 的函数来修改它，并将其绑定到按钮的点击事件．  \n总结我们想要做的事情：\n\n- 执行 `setup` 函数以从返回值获取用于获取 vnode 的函数\n- 使传递给 `reactive` 函数的对象变为响应式\n- 当按钮被点击时，状态被更新\n- 跟踪状态更新，重新执行渲染函数，并重绘屏幕\n\n## 什么是响应式系统？\n\n现在，让我们回顾一下什么是响应式．  \n让我们参考官方文档．\n\n> 响应式对象是 JavaScript 代理，其行为类似于普通对象。不同之处在于 Vue 可以跟踪响应式对象上的属性访问和更改。\n\n[来源](https://vuejs.org/guide/essentials/reactivity-fundamentals)\n\n> Vue 最独特的功能之一是其谦逊的响应式系统。组件的状态由响应式 JavaScript 对象组成。当状态改变时，视图会更新。\n\n[来源](https://vuejs.org/guide/extras/reactivity-in-depth.html)\n\n总之，\"响应式对象在有变化时更新屏幕\"．  \n让我们暂时搁置如何实现这一点，并实现前面提到的开发者接口．\n\n## setup 函数的实现\n\n我们需要做的非常简单．  \n我们接收 `setup` 选项并执行它，然后我们可以像之前的 `render` 选项一样使用它．\n\n编辑 `~/packages/runtime-core/componentOptions.ts`：\n\n```ts\nexport type ComponentOptions = {\n  render?: Function\n  setup?: () => Function // 添加\n}\n```\n\n然后使用它：\n\n```ts\n// createAppAPI\n\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    updateComponent()\n  },\n}\n```\n\n```ts\n// playground\n\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // 将来在这里定义状态\n    // const state = reactive({ count: 0 })\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n        h(\n          'button',\n          {\n            onClick() {\n              alert('Hello world!')\n            },\n          },\n          ['click me!'],\n        ),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n嗯，就是这样．  \n实际上，我们希望在状态改变时执行这个 `updateComponent`．\n\n## 代理对象\n\n这是这次的主要主题．我想在状态以某种方式改变时执行 `updateComponent`．\n\n关键是一个名为 Proxy 的对象．\n\n<KawaikoNote variant=\"question\" title=\"Proxy 是什么？\">\n\nProxy 是 JavaScript 的标准功能，不是 Vue.js 发明的．\\\n可以理解为「监视和自定义对象访问的机制」！\\\n通过它，我们可以检测到「值被读取」或「值被修改」．\n\n</KawaikoNote>\n\n首先，让我解释一下它们，而不是关于响应式系统的实现方法．\n\nhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy\n\nProxy 是一个非常有趣的对象．\n\n您可以通过将对象作为参数传递并像这样使用 `new` 来使用它：\n\n```ts\nconst o = new Proxy({ value: 1 }, {})\nconsole.log(o.value) // 1\n```\n\n在这个例子中，`o` 的行为几乎与普通对象相同．\n\n现在，有趣的是 Proxy 可以接受第二个参数并注册一个处理器．\n这个处理器是对象操作的处理器．请看以下示例：\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n\n  {\n    get(target, key, receiver) {\n      console.log(`target:${target}, key: ${key}`)\n      return target[key]\n    },\n  },\n)\n```\n\n在这个例子中，我们正在为生成的对象编写设置．\n具体来说，当访问（get）此对象的属性时，原始对象（target）和访问的键名将输出到控制台．\n让我们在浏览器或其他地方检查操作．\n\n![Proxy get trap console output](/figures/10-minimum-example/reactivity/proxy-get-trap.png)\n\n您可以看到为从此 Proxy 生成的对象的属性读取值而设置的 set 处理正在执行．\n\n同样，您也可以为 set 设置它．\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n  {\n    set(target, key, value, receiver) {\n      console.log('hello from setter')\n      target[key] = value\n      return true\n    },\n  },\n)\n```\n\n![Proxy set trap console output](/figures/10-minimum-example/reactivity/proxy-set-trap.png)\n\n<KawaikoNote variant=\"funny\" title=\"这就是响应式的秘密！\">\n\n用 get 检测「读取」，用 set 检测「写入」...\\\n也就是说，在 set 的时机调用「更新屏幕的处理」，就能实现 **值变化时自动更新屏幕** 的魔法！\n\n</KawaikoNote>\n\n这就是理解 Proxy 的程度．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/035-try-implementing-a-minimum-reactivity-system.md",
    "content": "# 尝试实现最小响应式系统\n\n## 使用 Proxy 的响应式机制\n\n::: info 与当前 `vuejs/core` 设计的差异\n截至 2024 年 12 月，Vue.js 的响应式系统采用基于双向链表的观察者模式．\\\n这个实现在 [Refactor reactivity system to use version counting and doubly-linked list tracking](https://github.com/vuejs/core/pull/10397) 中引入，对性能改进做出了重大贡献．  \n\n然而，对于第一次实现响应式系统的人来说，这可能有些难以理解．在本章中，我们将创建传统（优化前）系统的简化实现．\\\n有关更接近当前实现的系统的更详细解释，请参考 [响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)．\n\n另一个重大改进 [feat(reactivity): more efficient reactivity system](https://github.com/vuejs/core/pull/5912) 将在单独的章节中介绍．  \n:::\n\n为了再次明确目的，这次的目的是\"在状态改变时执行 `updateComponent`\"．让我使用 Proxy 解释实现过程．\n\n首先，Vue.js 的响应式系统涉及 `target`，`Proxy`，`ReactiveEffect`，`Dep`，`track`，`trigger`，`targetMap` 和 `activeEffect`（目前是 `activeSub`）．\n\n<KawaikoNote variant=\"warning\" title=\"角色很多！\">\n\n突然出现了很多术语，但不要慌！\\\n如果我们一个一个地看每个角色，拼图的碎片就会拼在一起．\\\n首先，让我们目标是「大致把握全貌」．\n\n</KawaikoNote>\n\n首先，让我们谈谈 targetMap 的结构．\ntargetMap 是某个目标的键和 deps 的映射．\nTarget 指的是您想要使其响应式的对象，dep 指的是您想要执行的效果（函数）．您可以这样想．\n在代码中，它看起来像这样：\n\n```ts\ntype Target = any // 任何目标\ntype TargetKey = any // 目标拥有的任何键\n\nconst targetMap = new WeakMap<Target, KeyToDepMap>() // 在此模块中定义为全局变量\n\ntype KeyToDepMap = Map<TargetKey, Dep> // 目标的键和效果的映射\n\ntype Dep = Set<ReactiveEffect> // dep 有多个 ReactiveEffects\n\nclass ReactiveEffect {\n  constructor(\n    // 这里，您给出想要实际应用为效果的函数（在这种情况下是 updateComponent）\n    public fn: () => T,\n  ) {}\n}\n```\n\n这意味着为\"某个目标（对象）\"的\"某个键\"注册\"某个效果\"．\n\n仅仅看代码可能很难理解，所以这里有一个具体的例子和补充图表．\\\n考虑如下组件：\n\n```ts\nexport default defineComponent({\n  setup() {\n    const state1 = reactive({ name: \"John\", age: 20 })\n    const state2 = reactive({ count: 0 })\n\n    function onCountUpdated() {\n      console.log(\"count updated\")\n    }\n\n    watch(() => state2.count, onCountUpdated)\n\n    return () => h(\"p\", {}, `name: ${state1.name}`)\n  }\n})\n```\n\n虽然我们在本章中还没有实现 `watch`，但为了说明而写在这里．\\\n在这个组件中，targetMap 最终将形成如下：\n\n![targetMap structure](/figures/10-minimum-example/reactivity/target-map-structure.svg)\n\ntargetMap 的键是\"某个目标\"．在这个例子中，state1 和 state2 对应于此．\\\n这些目标拥有的键成为 targetMap 的键．\n与它们关联的效果成为值．\n\n在部分 `() => h(\"p\", {}, name: ${state1.name})` 中，映射 `state1->name->updateComponentFn` 被注册，在部分 `watch(() => state2.count, onCountUpdated)` 中，映射 `state2->count->onCountUpdated` 被注册．\n\n这个基本结构负责其余部分，然后我们考虑如何创建（注册）targetMap 以及如何执行效果．\n\n<KawaikoNote variant=\"funny\" title=\"简单地想\">\n\n**targetMap** 是一个记录「谁影响谁」的笔记本．\\\n当 `state1.name` 改变时 → 运行 `updateComponent`\\\n当 `state2.count` 改变时 → 运行 `onCountUpdated`\\\n它记录了这些关系！\n\n</KawaikoNote>\n\n这就是 `track` 和 `trigger` 概念的用武之地．\n顾名思义，`track` 是在 `targetMap` 中注册的函数，`trigger` 是从 `targetMap` 检索效果并执行它的函数．\n\n```ts\nexport function track(target: object, key: unknown) {\n  // ..\n}\n\nexport function trigger(target: object, key?: unknown) {\n  // ..\n}\n```\n\n这些 `track` 和 `trigger` 在 Proxy 的 get 和 set 处理器中实现．\n\n```ts\nconst state = new Proxy(\n  { count: 1 },\n  {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  },\n)\n```\n\n生成此 Proxy 的 API 是 reactive 函数．\n\n```ts\nfunction reactive<T>(target: T) {\n  return new Proxy(target, {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n![reactive track and trigger flow](/figures/10-minimum-example/reactivity/reactive-track-trigger.svg)\n\n在这里，您可能会注意到一个缺失的元素．那就是\"在 track 中注册哪个函数？\"．\n答案是 `activeEffect` 的概念．\n这也像 targetMap 一样在此模块中定义为全局变量，并在 ReactiveEffect 的 `run` 方法中设置．\n\n```ts\nlet activeEffect: ReactiveEffect | undefined\n\nclass ReactiveEffect {\n  constructor(\n    // 这里，您给出想要实际应用为效果的函数（在这种情况下是 updateComponent）\n    public fn: () => T,\n  ) {}\n\n  run() {\n    activeEffect = this\n    return this.fn()\n  }\n}\n```\n\n要理解它是如何工作的，想象一个这样的组件．\n\n```ts\n{\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return function render() {\n      return h(\"div\", { id: \"my-app\" }, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\n          \"button\",\n          {\n            onClick: increment,\n          },\n          [\"increment\"]\n        ),\n      ]);\n    };\n  },\n}\n```\n\n在内部，响应式是这样形成的．\n\n```ts\n// chibivue 内部的实现\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    const effect = new ReactiveEffect(updateComponent)\n    effect.run()\n  },\n}\n```\n\n逐步解释，首先执行 `setup` 函数．\\\n此时生成响应式代理．换句话说，在此处创建的代理上执行的任何操作都将按照代理中配置的方式运行．\n\n```ts\nconst state = reactive({ count: 0 }) // 生成代理\n```\n\n接下来，我们传递 `updateComponent` 来创建 `ReactiveEffect`（观察者端）．\n\n```ts\nconst effect = new ReactiveEffect(updateComponent)\n```\n\n在 `updateComponent` 中使用的 `componentRender` 是 `setup` 的 `返回值` 的函数，这个函数引用由代理创建的对象．\n\n```ts\nfunction render() {\n  return h('div', { id: 'my-app' }, [\n    h('p', {}, [`count: ${state.count}`]), // 引用由代理创建的对象\n    h(\n      'button',\n      {\n        onClick: increment,\n      },\n      ['increment'],\n    ),\n  ])\n}\n```\n\n当这个函数实际执行时，`state.count` 的 `getter` 函数被执行，`track` 被触发．\n在这种情况下，让我们执行效果．\n\n```ts\neffect.run()\n```\n\n然后，`updateComponent`（带有 `updateComponent` 的 ReactiveEffect）被设置为 `activeEffect`．\n当在此状态下触发 `track` 时，`state.count` 和 `updateComponent`（带有 `updateComponent` 的 ReactiveEffect）的映射在 `targetMap` 中注册．\n这就是响应式的形成方式．\n\n现在，让我们考虑执行 `increment` 时会发生什么．\n由于 `increment` 正在重写 `state.count`，`setter` 被执行，`trigger` 被触发．\n`trigger` 基于 `state` 和 `count` 从 `targetMap` 中找到并执行 `effect`（在这种情况下是 updateComponent）．\n这就是屏幕更新的触发方式！\n\n这使我们能够实现响应式．\n\n这有点复杂，所以让我们用图表总结一下．\n\n![Reactivity setup flow during mount](/figures/10-minimum-example/reactivity/reactivity-setup-flow.svg)\n\n## 基于这些，让我们实现它．\n\n最困难的部分是理解到这一点的一切，所以一旦您理解了，您所要做的就是编写源代码．\n然而，即使您只理解上述内容，可能有些人在不知道实际发生什么的情况下无法理解．\n对于这些人，让我们首先在这里尝试实现它．然后，在阅读实际代码时，请参考前面的部分！\n\n首先，让我们创建必要的文件．我们将在 `packages/reactivity` 中创建它们．\n在这里，我们将尽可能地意识到原始 Vue 的配置．\n\n```sh\npwd # ~\nmkdir packages/reactivity\n\ntouch packages/reactivity/index.ts\n\ntouch packages/reactivity/dep.ts\ntouch packages/reactivity/effect.ts\ntouch packages/reactivity/reactive.ts\ntouch packages/reactivity/baseHandler.ts\n```\n\n像往常一样，`index.ts` 只是导出，所以我不会详细解释．在这里导出您想要从 reactivity 外部包使用的内容．\n\n接下来是 `dep.ts`．\n\n```ts\nimport { type ReactiveEffect } from './effect'\n\nexport type Dep = Set<ReactiveEffect>\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects)\n  return dep\n}\n```\n\n还没有 `effect` 的定义，但我们稍后会实现它，所以没关系．\n\n接下来是 `effect.ts`．\n\n```ts\nimport { Dep, createDep } from './dep'\n\ntype KeyToDepMap = Map<any, Dep>\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nexport let activeEffect: ReactiveEffect | undefined\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    // ※ 在执行 fn 之前保存 activeEffect，执行后恢复它。\n    // 如果您不这样做，它将一个接一个地被覆盖并表现出意外行为。（完成后让我们将其恢复到原始状态）\n    let parent: ReactiveEffect | undefined = activeEffect\n    activeEffect = this\n    const res = this.fn()\n    activeEffect = parent\n    return res\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target)\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()))\n  }\n\n  let dep = depsMap.get(key)\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()))\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect)\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  const dep = depsMap.get(key)\n\n  if (dep) {\n    const effects = [...dep]\n    for (const effect of effects) {\n      effect.run()\n    }\n  }\n}\n```\n\n到目前为止我还没有解释 `track` 和 `trigger` 的内容，但它们只是从 `targetMap` 注册和检索并执行它们，所以请尝试仔细阅读它们．\n\n接下来是 `baseHandler.ts`．在这里，我们定义响应式代理的处理器．\n嗯，您可以直接在 `reactive` 中实现它，但我遵循了原始 Vue，因为它是这样的．\n实际上，有各种代理，如 `readonly` 和 `shallow`，所以想法是在这里实现这些代理的处理器．（虽然这次我们不会这样做）\n\n```ts\nimport { track, trigger } from './effect'\nimport { reactive } from './reactive'\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key)\n\n    const res = Reflect.get(target, key, receiver)\n    // 如果它是一个对象，使其响应式（这也允许嵌套对象是响应式的）。\n    if (res !== null && typeof res === 'object') {\n      return reactive(res)\n    }\n\n    return res\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key]\n    Reflect.set(target, key, value, receiver)\n    // 检查值是否已更改\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key)\n    }\n    return true\n  },\n}\n\nconst hasChanged = (value: any, oldValue: any): boolean =>\n  !Object.is(value, oldValue)\n```\n\n在这里，出现了 `Reflect`，它类似于 `Proxy`，但 `Proxy` 是为对象编写元设置，而 `Reflect` 是对现有对象执行操作．\n`Proxy` 和 `Reflect` 都是 JS 引擎中与对象相关的元编程 API，它们允许您与正常使用对象相比执行元操作．\n您可以执行更改对象的函数，执行读取对象的函数，检查键是否存在，并执行各种元操作．\n现在，可以理解 `Proxy` 是在创建对象阶段的元设置，`Reflect` 是对现有对象的元操作．\n\n接下来是 `reactive.ts`．\n\n```ts\nimport { mutableHandlers } from './baseHandler'\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\n现在 `reactive` 的实现完成了，让我们尝试在挂载时使用它们．\n`~/packages/runtime-core/apiCreateApp.ts`．\n\n```ts\nimport { ReactiveEffect } from '../reactivity'\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const componentRender = rootComponent.setup!()\n\n        const updateComponent = () => {\n          const vnode = componentRender()\n          render(vnode, rootContainer)\n        }\n\n        // 从这里\n        const effect = new ReactiveEffect(updateComponent)\n        effect.run()\n        // 到这里\n      },\n    }\n\n    return app\n  }\n}\n```\n\n现在，让我们在游乐场中尝试它．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n哎呀...\n\n渲染现在工作正常，但似乎有些不对劲．\n嗯，这并不奇怪，因为在 `updateComponent` 中，我们每次都创建元素．\n所以，让我们在每次渲染之前删除所有元素．\n\n![Reactive example mistake in the browser](/figures/10-minimum-example/reactivity/reactive-example-mistake.png)\n\n像这样修改 `~/packages/runtime-core/renderer.ts` 中的 `render` 函数：\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild) // 添加代码以删除所有元素\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\n现在，这样如何？\n\n![Reactive example rendered in the browser](/figures/10-minimum-example/reactivity/reactive-example-result.png)\n\n现在似乎工作正常！\n\n现在我们可以使用 `reactive` 更新屏幕！\n\n<KawaikoNote variant=\"surprise\" title=\"恭喜！\">\n\n响应式系统的基础已经完成！\\\n你理解了 Vue.js「值变化时自动更新屏幕」魔法背后的秘密了吗？\\\n克服了这一关，你对 Vue.js 内部已经有了很深的理解！\n\n</KawaikoNote>\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/030_reactive_system)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/040-minimum-virtual-dom.md",
    "content": "# 最小虚拟 DOM\n\n## 虚拟 DOM 用于什么？\n\n<KawaikoNote variant=\"question\" title=\"为什么使用虚拟 DOM？\">\n\n虚拟 DOM 的目的是\"差异更新\"．\n它识别已更改的部分，只执行必要的 DOM 操作！\n（但是，虚拟 DOM 本身也有开销，所以它不是万能的）\n\n</KawaikoNote>\n\n通过在上一章中引入响应式系统，我们能够动态更新屏幕．让我们再次查看当前渲染函数的内容．\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild)\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\n有些人可能在上一章中注意到这个函数中有很多浪费．\n\n看看游乐场．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n```\n\n问题是当执行 increment 时，只有 `count: ${state.count}` 部分发生变化，但在 renderVNode 中，所有 DOM 元素都被删除并从头重新创建．这感觉非常浪费．\\\n虽然现在看起来工作正常，因为它仍然很小，但您可以很容易地想象，如果您在开发 Web 应用程序时每次都必须从头重新创建复杂的 DOM，性能将大大降低．\\\n因此，由于我们已经有了虚拟 DOM，我们希望实现一个比较当前虚拟 DOM 与之前虚拟 DOM 的实现，并仅使用 DOM 操作更新存在差异的部分．\\\n现在，这是本章的主要主题．\n\n让我们看看我们想在源代码中做什么．当我们有像上面这样的组件时，渲染函数的返回值变成如下的虚拟 DOM．在初始渲染时，计数为 0，所以它看起来像这样：\n\n```ts\nconst vnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 0`]\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      [\"increment\"]\n    }\n  ]\n}\n```\n\n让我们保留这个 vnode 并为下一次渲染准备另一个 vnode．以下是第一次点击按钮时的 vnode：\n\n```ts\nconst nextVnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 1`] // 只想更新这部分\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      [\"increment\"]\n    }\n  ]\n}\n```\n\n现在，有了这两个 vnodes，屏幕处于 vnode 的状态（在它变成 nextVnode 之前）．\\\n我们希望将这两个传递给一个名为 patch 的函数，并仅渲染差异．\n\n```ts\nconst vnode = {...}\nconst nextVnode = {...}\npatch(vnode, nextVnode, container)\n```\n\n我之前介绍了函数名，但这种差异渲染称为\"patch\"．\\\n有时也称为\"reconciliation\"．通过使用这两个虚拟 DOM，您可以高效地更新屏幕．\n\n## 在实现 patch 函数之前\n\n这与主要主题没有直接关系，但让我们在这里做一个轻微的重构（因为这对我们接下来要讨论的内容很方便）．\\\n让我们在 vnode.ts 中创建一个名为 createVNode 的函数，并让 h 函数调用它．\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: unknown,\n): VNode {\n  const vnode: VNode = { type, props, children: [] }\n  return vnode\n}\n```\n\n也更改 h 函数．\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return createVNode(type, props, children)\n}\n```\n\n现在，让我们进入正题．到目前为止，VNode 拥有的小元素的类型一直是 `(Vnode | string)[]`，但仅将 Text 视为字符串是不够的，所以让我们尝试将其统一为 VNode．\\\nText 不仅仅是一个字符串，它作为 HTML TextElement 存在，所以它包含的信息比仅仅一个字符串更多．\\\n我们希望将其视为 VNode 以便处理周围的信息．\\\n具体来说，让我们使用符号 Text 将其作为 VNode 的类型．\\\n例如，当有像 `\"hello\"` 这样的文本时，\n\n```ts\n{\n  type: Text,\n  props: null,\n  children: \"hello\"\n}\n```\n\n是表示形式．\n\n另外，这里需要注意的一点是，当执行 h 函数时，我们将继续使用传统的表达式，我们将通过在渲染函数中应用名为 normalize 的函数来转换它，以表示如上所述的 Text．这样做是为了匹配原始的 Vue.js．\n\n`~/packages/runtime-core/vnode.ts`;\n\n```ts\nexport const Text = Symbol();\n\nexport type VNodeTypes = string | typeof Text;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\n// 规范化后的类型\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(..){..} // 省略\n\n// 实现 normalize 函数（在 renderer.ts 中使用）\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    // 将字符串转换为前面介绍的所需形式\n    return createVNode(Text, null, String(child));\n  }\n}\n```\n\n现在 Text 可以被视为 VNode．\n\n## patch 函数的设计\n\n首先，让我们看看代码库中 patch 函数的设计．\\\n（我们不需要在这里实现它，只需理解它．）\\\npatch 函数比较两个 vnodes，vnode1 和 vnode2．但是，vnode1 最初不存在．\\\n因此，patch 函数分为两个过程：\"初始（从 vnode2 生成 dom）\"和\"更新 vnode1 和 vnode2 之间的差异\"．\\\n这些过程分别命名为\"mount\"和\"patch\"．\\\n它们分别对 ElementNode 和 TextNode 执行（结合为\"process\"，每个都有\"mount\"和\"patch\"名称）．\n\n<img   \n    src=\"/figures/10-minimum-example/virtual-dom/patch-function-architecture.svg\"\n    alt=\"Patch Function Architecture\"   \n    style=\"background-color: white;\"\n/>\n\n```ts\nconst patch = (\n  n1: VNode | string | null,\n  n2: VNode | string,\n  container: HostElement,\n) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else {\n    processElement(n1, n2, container)\n  }\n}\n\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: HostElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    patchElement(n1, n2)\n  }\n}\n\nconst processText = (n1: string | null, n2: string, container: HostElement) => {\n  if (n1 == null) {\n    mountText(n2, container)\n  } else {\n    patchText(n1, n2)\n  }\n}\n```\n\n## 实际实现\n\n现在让我们实际实现虚拟 DOM 的 patch 函数．\\\n首先，我们希望在 vnode 挂载时在 vnode 中有对实际 DOM 的引用，无论它是 Element 还是 Text．\\\n所以我们向 vnode 添加\"el\"属性．\n\n`~/packages/runtime-core/vnode.ts`\n\n```ts\nexport interface VNode<HostNode = RendererNode> {\n  type: VNodeTypes\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined // [!code ++]\n}\n```\n\n现在让我们转到 `~/packages/runtime-core/renderer.ts`．\\\n我们将在 `createRenderer` 函数内部实现它并删除 `renderVNode` 函数．\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  // .\n  // .\n  // .\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2\n    if (type === Text) {\n      // processText(n1, n2, container);\n    } else {\n      // processElement(n1, n2, container);\n    }\n  }\n}\n```\n\n让我们从 `processElement` 和 `mountElement` 开始实现．\n\n```ts\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    // patchElement(n1, n2);\n  }\n}\n\nconst mountElement = (vnode: VNode, container: RendererElement) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children, el) // TODO:\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\n由于它是一个元素，我们还需要挂载其子元素．\\\n让我们使用我们之前创建的 `normalize` 函数．\n\n```ts\nconst mountChildren = (children: VNode[], container: RendererElement) => {\n  for (let i = 0; i < children.length; i++) {\n    const child = (children[i] = normalizeVNode(children[i]))\n    patch(null, child, container)\n  }\n}\n```\n\n这样，我们已经实现了元素的挂载．\\\n接下来，让我们转到挂载 Text．\\\n但是，这只是一个简单的 DOM 操作．\\\n在设计说明中，我们将其分为 `mountText` 和 `patchText` 函数，但由于处理不多，并且预计将来不会变得更复杂，让我们直接编写它．\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // TODO: patch\n  }\n}\n```\n\n现在，随着初始渲染的挂载完成，让我们将一些处理从 `createAppAPI` 中的 `mount` 函数移动到 `render` 函数，以便我们可以保存两个 vnodes．\\\n具体来说，我们将 `rootComponent` 传递给 `render` 函数并在其中执行 ReactiveEffect 注册．\n\n```ts\nreturn function createApp(rootComponent) {\n  const app: App = {\n    mount(rootContainer: HostElement) {\n      // 只传递 rootComponent\n      render(rootComponent, rootContainer)\n    },\n  }\n}\n```\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\n现在，让我们尝试在游乐场中渲染，看看它是否工作！\n\n由于我们还没有实现 patch 函数，屏幕不会更新．\n\n所以，让我们继续编写 patch 函数．\n\n```ts\nconst patchElement = (n1: VNode, n2: VNode) => {\n  const el = (n2.el = n1.el!)\n\n  const props = n2.props\n\n  patchChildren(n1, n2, el)\n\n  for (const key in props) {\n    if (props[key] !== n1.props?.[key]) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n}\n\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\nText 节点也是如此．\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // 添加 patch 逻辑\n    const el = (n2.el = n1.el!)\n    if (n2.children !== n1.children) {\n      hostSetText(el, n2.children as string)\n    }\n  }\n}\n```\n\n※ 关于 patchChildren，通常我们需要通过添加 key 属性来处理动态长度的子元素，但由于我们正在实现一个小的虚拟 DOM，我们不会在这里涵盖其实用性．\\\n如果您感兴趣，请参考基础虚拟 DOM 部分．\\\n在这里，我们的目标是在一定程度上理解虚拟 DOM 的实现和作用．\n\n现在我们可以执行差异渲染，让我们看看游乐场．\n\n![patch rendering result in the browser](/figures/10-minimum-example/virtual-dom/patch-rendering-result.png)\n\n我们已经成功使用虚拟 DOM 实现了补丁！！！！！恭喜！\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/040_vdom_system)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/050-minimum-component.md",
    "content": "# 我想使用基于组件的方法进行开发\n\n<KawaikoNote variant=\"question\" title=\"为什么使用组件？\">\n\n组件是将 UI 分成可重用部分的机制．\n不用多次写同样的按钮，只需定义一次就可以重复使用！\n\n</KawaikoNote>\n\n## 基于整理现有实现的思考\n\n到目前为止，我们已经小规模地实现了 createApp API，响应式系统和虚拟 DOM 系统．\\\n通过当前的实现，我们可以使用响应式系统动态更改 UI，并使用虚拟 DOM 系统执行差异渲染．\\\n然而，作为开发者接口，所有内容都写在 createAppAPI 中．\\\n实际上，我想更多地分割文件并实现通用组件以实现可重用性．\\\n首先，让我们回顾一下现有实现中当前混乱的部分．请查看 renderer.ts 中的 render 函数．\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n  let n2: VNode = null!\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\n在 render 函数中，直接定义了关于根组件的信息．\\\n实际上，n1，n2，updateComponent 和 effect 对每个组件都存在．\\\n事实上，从现在开始，我想在用户端定义组件（在某种意义上是构造函数）并实例化它．\\\n我希望实例具有 n1，n2 和 updateComponent 等属性．\\\n因此，让我们考虑将这些封装为组件实例．\n\n让我们在 `~/packages/runtime-core/component.ts` 中定义一个叫做 `ComponentInternalInstance` 的东西．\\\n这将是实例的类型．\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component // 原始用户定义的组件（旧的 rootComponent（实际上不仅仅是根组件））\n  vnode: VNode // 稍后解释\n  subTree: VNode // 旧的 n1\n  next: VNode | null // 旧的 n2\n  effect: ReactiveEffect // 旧的 effect\n  render: InternalRenderFunction // 旧的 componentRender\n  update: () => void // 旧的 updateComponent\n  isMounted: boolean\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild\n}\n```\n\n这个实例拥有的 vnode，subTree 和 next 属性有点复杂，但从现在开始，我们将实现它，以便可以将 ConcreteComponent 指定为 VNode 的类型．\\\n在 instance.vnode 中，我们将保留 VNode 本身．\\\n而 subTree 和 next 将保存该组件的渲染结果 VNode．（这与之前的 n1 和 n2 相同）\n\n在图像方面，\n\n```ts\nconst MyComponent = {\n  setup() {\n    return h('p', {}, ['hello'])\n  },\n}\n\nconst App = {\n  setup() {\n    return h(MyComponent, {}, [])\n  },\n}\n```\n\n您可以像这样使用它，如果让实例成为 MyComponent 的实例，instance.vnode 将保存 `h(MyComponent, {}, [])` 的结果，instance.subTree 将保存 `h(\"p\", {}, [\"hello\"])` 的结果．\n\n现在，让我们实现它，以便您可以将组件指定为 h 函数的第一个参数．\\\n但是，这只是接收定义组件的对象作为类型的问题．\\\n在 `~/packages/runtime-core/vnode.ts` 中\n\n```ts\nexport type VNodeTypes = string | typeof Text | object // 添加 object;\n```\n\n在 `~/packages/runtime-core/h.ts` 中\n\n```ts\nexport function h(\n  type: string | object, // 添加 object\n  props: VNodeProps\n) {..}\n```\n\n让我们也确保 VNode 有一个组件实例．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  component: ComponentInternalInstance | null // 添加\n}\n```\n\n因此，渲染器也需要处理组件．\\\n实现类似于 `processElement` 和 `processText` 的 `processComponent` 来处理组件，并且还实现 `mountComponent` 和 `patchComponent`（或 `updateComponent`）．\n\n首先，让我们从概述和详细说明开始．\n\n![Component instance flow](/figures/10-minimum-example/minimum-component/component-instance-flow.svg)\n\n```ts\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    // 添加分支\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountComponent(n2, container)\n  } else {\n    updateComponent(n1, n2)\n  }\n}\n\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // TODO:\n}\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  // TODO:\n}\n```\n\n现在，让我们看看 `mountComponent`．有三件事要做．\n\n1. 创建组件的实例．\n2. 执行 `setup` 函数并将结果存储在实例中．\n3. 创建 `ReactiveEffect` 并将其存储在实例中．\n\n首先，让我们在 `component.ts` 中实现一个函数来创建组件的实例（类似于构造函数）．\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    type,\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n    isMounted: false,\n  }\n\n  return instance\n}\n```\n\n虽然每个属性的类型都是非空的，但我们在创建实例时用 null 初始化它们（遵循原始 Vue.js 的设计）．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n  // TODO: setup component\n  // TODO: setup effect\n}\n```\n\n接下来是 `setup` 函数．\\\n我们需要将之前直接在 `render` 函数中编写的代码移动到这里，并将结果存储在实例中而不是使用变量．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  // TODO: setup effect\n}\n```\n\n最后，让我们将创建 effect 的代码合并到一个名为 `setupRenderEffect` 的函数中．\\\n同样，主要任务是将之前直接在 `render` 函数中实现的代码移动到这里，同时利用实例的状态．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render } = instance\n\n    if (!instance.isMounted) {\n      // mount process\n      const subTree = (instance.subTree = normalizeVNode(render()))\n      patch(null, subTree, container)\n      initialVNode.el = subTree.el\n      instance.isMounted = true\n    } else {\n      // patch process\n      let { next, vnode } = instance\n\n      if (next) {\n        next.el = vnode.el\n        next.component = instance\n        instance.vnode = next\n        instance.next = null\n      } else {\n        next = vnode\n      }\n\n      const prevTree = instance.subTree\n      const nextTree = normalizeVNode(render())\n      instance.subTree = nextTree\n\n      patch(prevTree, nextTree, hostParentNode(prevTree.el!)!) // ※ 1\n      next.el = nextTree.el\n    }\n  }\n\n  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn))\n  const update = (instance.update = () => effect.run()) // 注册到 instance.update\n  update()\n}\n```\n\n※ 1: 请在 `nodeOps` 中实现一个名为 `parentNode` 的函数，用于检索父 Node．\n\n```ts\nparentNode: (node) => {\n    return node.parentNode;\n},\n```\n\n我认为这并不特别困难，尽管有点长．\\\n在 `setupRenderEffect` 函数中，更新函数被注册为实例的 `update` 方法，所以在 `updateComponent` 中，我们只需要调用该函数．\n\n```ts\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!\n  instance.next = n2\n  instance.update()\n}\n```\n\n最后，由于到目前为止在 `render` 函数中定义的实现不再需要，我们将删除它．\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  patch(null, vnode, container)\n}\n```\n\n现在我们可以渲染组件了．让我们尝试创建一个 `playground` 组件作为示例．\\\n通过这种方式，我们可以将渲染分为组件．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst CounterComponent = {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/051-component-props.md",
    "content": "# 组件 Props\n\n## 开发者接口\n\n让我们从 props 开始．\\\n让我们思考一下最终的开发者接口．\\\n让我们考虑将 props 作为 `setup` 函数的第一个参数传递．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\n## 实现\n\n基于此，让我们思考一下我们想在 `ComponentInternalInstance` 中拥有的信息．\\\n我们需要指定为 `props: { message: { type: String } }` 的 props 定义，以及一个实际保存 props 值的属性，所以我们添加以下内容：\n\n```ts\nexport type Data = Record<string, unknown>\n\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  propsOptions: Props // 保存像 `props: { message: { type: String } }` 这样的对象\n\n  props: Data // 保存从父组件传递的实际数据（在这种情况下，它将是像 `{ message: \"hello\" }` 这样的东西）\n}\n```\n\n创建一个名为 `~/packages/runtime-core/componentProps.ts` 的新文件，内容如下：\n\n```ts\nexport type Props = Record<string, PropOptions | null>\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null\n  required?: boolean\n  default?: null | undefined | object\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} }\n```\n\n在实现组件时将其添加到选项中．\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any> // 添加\n  setup?: () => Function\n  render?: Function\n}\n```\n\n当使用 `createComponentInstance` 生成实例时，在生成实例时将 propsOptions 设置到实例中．\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    propsOptions: type.props || {},\n    props: {},\n```\n\n让我们思考如何形成 `instance.props`．\\\n在组件挂载时，根据 propsOptions 过滤 vnode 持有的 props．\\\n使用 `reactive` 函数将过滤后的对象转换为响应式对象，并将其分配给 `instance.props`．\n\n在 `componentProps.ts` 中实现一个名为 `initProps` 的函数来执行这一系列步骤．\n\n```ts\nexport function initProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const props: Data = {}\n  setFullProps(instance, rawProps, props)\n  instance.props = reactive(props)\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      if (options && options.hasOwnProperty(key)) {\n        props[key] = value\n      }\n    }\n  }\n}\n```\n\n在挂载时实际执行 `initProps`，并将 props 作为参数传递给 `setup` 函数．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    // init props\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup(\n        instance.props // 将 props 传递给 setup\n      ) as InternalRenderFunction;\n    }\n    // .\n    // .\n    // .\n}\n```\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (props: Record<string, any>) => Function // 接收 props\n  render?: Function\n}\n```\n\n此时，props 应该传递给子组件，所以让我们在游乐场中检查它．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\n但是，这还不够，因为当 props 更改时渲染不会更新．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```\n\n要使此组件工作，我们需要在 `componentProps.ts` 中实现 `updateProps` 并在组件更新时执行它．\n\n`~/packages/runtime-core/componentProps.ts`\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  Object.assign(props, rawProps)\n}\n```\n\n让我们整理一下组件更新处理的流程．\\\n当父组件重新渲染时，传递给子组件的 props 可能会改变．\\\n流程如下：\n\n1. 父组件的 `render` 函数被执行，为子组件生成新的 VNode\n2. 在 `patch` 处理中，`processComponent` 被调用，比较现有组件（`n1`）和新的 VNode（`n2`）\n3. 如果存在现有组件，则调用 `updateComponent` 函数\n\n首先，在 `ComponentInternalInstance` 中添加 `next` 属性．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  vnode: VNode // 当前的VNode\n  next: VNode | null // 当有来自父组件的更新请求时，新的VNode会被设置在这里\n  // .\n  // .\n}\n```\n\n接下来，在 `processComponent` 中实现已挂载组件的更新处理．\n\n```ts\nconst processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  if (n1 == null) {\n    mountComponent(n2, container);\n  } else {\n    updateComponent(n1, n2); // 添加\n  }\n};\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!; // 将实例引用从旧VNode继承到新VNode\n  instance.next = n2; // 将新VNode设置到next\n  instance.update(); // 触发组件更新\n};\n```\n\n在 `updateComponent` 中，我们将新的 VNode（`n2`）设置到 `instance.next`，然后调用 `instance.update()`．\\\n这会触发 `componentUpdateFn` 的执行．\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          // 当有来自父组件的更新请求时（例如，props 改变了）\n          next.el = vnode.el; // 将当前DOM元素引用继承到新VNode\n          next.component = instance; // 将实例引用设置到新VNode\n          instance.vnode = next; // 将实例的\"当前VNode\"切换为新的\n          instance.next = null; // 已处理完毕，重置为null\n          updateProps(instance, next.props); // 用新的props更新实例的props\n        }\n        // 如果next不存在，则是由于组件自身响应式状态变化而导致的重新渲染\n```\n\n当 `instance.next` 存在时，意味着有来自父组件的更新请求（如 props 改变）．\\\n在这种情况下，我们先将新 VNode 的信息反映到实例中，然后再更新 props．\\\n当 `instance.next` 不存在时，则是由于组件自身内部状态（响应式值）的变化而导致的重新渲染．\n\n如果屏幕更新了，那就没问题．\\\n现在，您可以使用 props 将数据传递给组件！做得很好！\n\n![Component props flow in the browser](/figures/10-minimum-example/component-props/props-flow.png)\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system2)\n\n作为附注，虽然这不是必需的，但让我们实现接收 kebab-case props 的能力，就像原始 Vue 中一样．\\\n此时，创建一个名为 `~/packages/shared` 的目录，并在其中创建一个名为 `general.ts` 的文件．\\\n这是定义通用函数的地方，不仅适用于 `runtime-core` 和 `runtime-dom`．\\\n按照原始 Vue，让我们实现 `hasOwn` 和 `camelize`．\n\n`~/packages/shared/general.ts`\n\n```ts\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nexport const hasOwn = (\n  val: object,\n  key: string | symbol,\n): key is keyof typeof val => hasOwnProperty.call(val, key)\n\nconst camelizeRE = /-(\\w)/g\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))\n}\n```\n\n让我们在 `componentProps.ts` 中使用 `camelize`．\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  // -------------------------------------------------------------- 这里\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value\n  })\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      // -------------------------------------------------------------- 这里\n      // kebab -> camel\n      let camelKey\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value\n      }\n    }\n  }\n}\n```\n\n现在您应该也能够处理 kebab-case 了．让我们在游乐场中检查它．\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: { someMessage: string }) {\n    return () => h('div', {}, [`someMessage: ${props.someMessage}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { 'some-message': state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/052-component-emits.md",
    "content": "# 组件 Emits\n\n## 开发者接口\n\n继 props 之后，让我们实现 emits．\\\nemits 的实现相对简单，所以会很快完成．\n\n在开发者接口方面，emits 将从 setup 函数的第二个参数接收．\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n## 实现\n\n与 props 类似，让我们创建一个名为 `~/packages/runtime-core/componentEmits.ts` 的文件并在那里实现它．\\\n`~/packages/runtime-core/componentEmits.ts`\n\n```ts\nexport function emit(\n  instance: ComponentInternalInstance,\n  event: string,\n  ...rawArgs: any[]\n) {\n  const props = instance.vnode.props || {}\n  let args = rawArgs\n\n  let handler =\n    props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]\n\n  if (handler) handler(...args)\n}\n```\n\n`~/packages/shared/general.ts`\n\n```ts\nexport const capitalize = (str: string) =>\n  str.charAt(0).toUpperCase() + str.slice(1)\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``)\n```\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  emit: (event: string, ...args: any[]) => void\n}\n\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    emit: null!, // to be set immediately\n  }\n\n  instance.emit = emit.bind(null, instance)\n  return instance\n}\n```\n\n您可以将此传递给 setup 函数．\n\n`~/packages/runtime-core/componentOptions.ts`\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function // 接收 ctx.emit\n  render?: Function\n}\n```\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      // 传递 emit\n      instance.render = component.setup(instance.props, {\n        emit: instance.emit,\n      }) as InternalRenderFunction;\n    }\n```\n\n让我们用我们之前假设的开发者接口示例来测试功能！  \n如果它正常工作，您现在可以使用 props/emit 在组件之间进行通信！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system3)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/060-template-compiler.md",
    "content": "# 理解模板编译器\n\n## 实际上，到目前为止我们已经拥有了运行所需的一切（？）\n\n到目前为止，我们已经实现了响应式系统，虚拟 DOM 和组件．\n虽然这些都非常小且不实用，但可以毫不夸张地说，我们已经理解了运行所需的整体配置元素．\n虽然每个元素本身的功能都不足，但感觉我们已经表面上过了一遍．\n\n从本章开始，我们将实现模板功能，使其更接近 Vue.js．但是，这些只是为了改善 DX，不会影响运行时．（严格来说，编译器优化可能会有影响，但由于这不是重点，我们假设它没有影响．）\\\n更具体地说，我们将扩展开发者接口以改善 DX，并\"最终将其转换为我们迄今为止制作的内部实现\"．\n\n## 这次我们想要实现的开发者接口\n\n目前，开发者接口看起来像这样．\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n目前，View 部分是使用 h 函数构建的．我们希望能够在 template 选项中编写模板，使其更接近原始 HTML．\\\n但是，一次实现各种东西是困难的，所以让我们从有限的功能集开始．\\\n现在，让我们将其分为以下任务：\n\n1. 能够渲染简单的标签，消息和静态属性．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n2. 能够渲染更复杂的 HTML．\n\n```ts\nconst app = createApp({\n  template: `\n    <div>\n      <p>hello</p>\n      <button> click me! </button>\n    </div>\n  `,\n})\n```\n\n3. 能够使用在 setup 函数中定义的内容．\n\n```ts\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n\n    return { count, increment }\n  },\n\n  template: `\n    <div>\n      <p>count: {{ count }}</p>\n      <button v-on:click=\"increment\"> click me! </button>\n    </div>\n  `,\n})\n```\n\n我们将进一步将每个任务分为更小的部分，但让我们大致分为这三个步骤．\n让我们从步骤 1 开始．\n\n## 编译器的作用\n\n现在，我们的目标开发者接口看起来像这样．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n首先，让我们谈谈什么是编译器．\n在编写软件时，您很快就会听到\"编译器\"这个词．\n\"编译\"意味着翻译，在软件领域，它通常用于表示从高级描述翻译到低级描述．\\\n您还记得本书开头的这个词吗？\n\n> 为了方便起见，我们将更接近原始 JS 的称为\"低级开发者接口\"。\n> 而且，重要的是要注意\"开始实现时，从低级部分开始\"。\n> 这样做的原因是，在许多情况下，高级描述被转换为低级描述并运行。\n> 换句话说，1 和 2 最终在内部转换为 3 的形式。\n> 这种转换的实现称为\"编译器\"。\n\n那么，为什么我们需要这个叫做编译器的东西呢？主要目的之一是\"改善开发体验\"．\n至少，如果提供了一个有效的低级接口，就可以仅使用这些函数进行开发．\n但是，考虑与功能无关的各种部分可能会很麻烦和困难，描述可能难以理解．因此，我们将仅重新开发接口部分，考虑用户的感受．\n\n在这方面，Vue.js 的目标是\"像原始 HTML 一样编写，并使用 Vue 提供的功能（指令等）方便地编写视图\"．\n而且，最终目标是 SFC．\\\n最近，随着 jsx/tsx 的流行，Vue 也提供这些作为开发者接口的选项．但是，这次，让我们尝试实现 Vue 的原始模板．\n\n我已经用长篇文章解释了它，但最终，我这次想要做的是实现将这样的代码翻译（编译）的能力：\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n转换为这样：\n\n```ts\nconst app = createApp({\n  render() {\n    return h('p', { class: 'hello' }, ['Hello World'])\n  },\n})\n```\n\n为了进一步缩小范围，就是这部分：\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\n让我们分几个阶段逐步实现它．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/061-template-compiler-impl.md",
    "content": "# 实现模板编译器\n\n## 实现方法\n\n基本方法是操作通过 template 选项传递的字符串来生成特定函数．\\\n让我们将编译器分为三个元素．\n\n### 解析\n\n解析涉及从给定字符串中提取必要信息．您可以这样想：\n\n```ts\nconst { tag, props, textContent } = parse(`<p class=\"hello\">Hello World</p>`)\nconsole.log(tag) // \"p\"\nconsole.log(prop) // { class: \"hello\" }\nconsole.log(textContent) // \"Hello World\"\n```\n\n### 代码生成\n\n代码生成基于解析结果生成代码（字符串）．\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"h('p', { class: 'hello' }, ['Hello World']);\"\n```\n\n### 函数对象生成\n\n函数对象生成基于 codegen 生成的代码（字符串）创建可执行函数．\\\n在 JavaScript 中，您可以使用 Function 构造函数从字符串生成函数．\n\n```ts\nconst f = new Function('return 1')\nconsole.log(f()) // 1\n\n// 如果您想定义参数，可以这样做\nconst add = new Function('a', 'b', 'return a + b')\nconsole.log(add(1, 1)) // 2\n```\n\n我们将使用这个来生成函数．\\\n这里需要注意的一点是，生成的函数只能处理在其内部定义的变量，所以我们需要在其中包含 h 函数等函数的导入．\n\n```ts\nimport * as runtimeDom from './runtime-dom'\nconst render = new Function('ChibiVue', code)(runtimeDom)\n```\n\n通过这样做，我们可以将 runtimeDom 作为 ChibiVue 接收，并在 codegen 阶段包含 h 函数，如下所示：\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"return () => { const { h } = ChibiVue; return h('p', { class: 'hello' }, ['Hello World']); }\"\n```\n\n换句话说，之前我们说我们会这样转换：\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\n但准确地说，我们这样转换：\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n\n// ↓\n\nChibiVue => {\n  return () => {\n    const { h } = ChibiVue\n    return h('p', { class: 'hello' }, ['Hello World'])\n  }\n}\n```\n\n并传递 runtimeDom 来生成 render 函数．\\\ncodegen 的责任是生成以下字符串：\n\n```ts\nconst code = `\n  return () => {\n      const { h } = ChibiVue;\n      return h(\"p\", { class: \"hello\" }, [\"Hello World\"]);\n  };\n`\n```\n\n## 实现\n\n一旦您理解了方法，让我们实现它．\\\n在 `~/packages` 中创建一个名为 `compiler-core` 的目录，并在其中创建 `index.ts`，`parse.ts` 和 `codegen.ts`．\n\n```sh\npwd # ~/\nmkdir packages/compiler-core\ntouch packages/compiler-core/index.ts\ntouch packages/compiler-core/parse.ts\ntouch packages/compiler-core/codegen.ts\n```\n\nindex.ts 像往常一样只用于导出．\n\n现在让我们从 parse 开始实现．\n`packages/compiler-core/parse.ts`\n\n```ts\nexport const baseParse = (\n  content: string,\n): { tag: string; props: Record<string, string>; textContent: string } => {\n  const matched = content.match(/<(\\w+)\\s+([^>]*)>([^<]*)<\\/\\1>/)\n  if (!matched) return { tag: '', props: {}, textContent: '' }\n\n  const [_, tag, attrs, textContent] = matched\n\n  const props: Record<string, string> = {}\n  attrs.replace(/(\\w+)=[\"']([^\"']*)[\"']/g, (_, key: string, value: string) => {\n    props[key] = value\n    return ''\n  })\n\n  return { tag, props, textContent }\n}\n```\n\n虽然这是一个使用正则表达式的非常简单的解析器，但对于第一次实现来说已经足够了．\n\n接下来，让我们生成代码．在 codegen.ts 中实现它．\\\n`packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = ({\n  tag,\n  props,\n  textContent,\n}: {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}): string => {\n  return `return () => {\n  const { h } = ChibiVue;\n  return h(\"${tag}\", { ${Object.entries(props)\n    .map(([k, v]) => `${k}: \"${v}\"`)\n    .join(', ')} }, [\"${textContent}\"]);\n}`\n}\n```\n\n现在，让我们实现一个通过组合这些从模板生成函数字符串的函数．\\\n创建一个名为 `packages/compiler-core/compile.ts` 的新文件．\n\n`packages/compiler-core/compile.ts`\n\n```ts\nimport { generate } from './codegen'\nimport { baseParse } from './parse'\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template)\n  const code = generate(parseResult)\n  return code\n}\n```\n\n这应该不会太困难．实际上，`compiler-core` 的责任到此结束．\n\n## 运行时编译器和构建过程编译器\n\n实际上，Vue 有两种类型的编译器．\\\n一种是在运行时（在浏览器中）运行的编译器，另一种是在构建过程中（如 Node.js）运行的编译器．\\\n具体来说，运行时编译器负责编译 template 选项或作为 HTML 提供的模板，而构建过程编译器负责编译 SFC（或 JSX）．\\\n我们当前实现的 template 选项属于前者．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\n作为 HTML 提供的模板是一个开发者接口，您可以在 HTML 中编写 Vue 模板．\\\n（通过 CDN 等快速将其合并到 HTML 中很方便．）\n\n```ts\nconst app = createApp()\napp.mount('#app')\n```\n\n```html\n<div id=\"app\">\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert('hello')\">click me!</button>\n</div>\n```\n\n这两种都需要编译，但编译是在浏览器中执行的．\n\n另一方面，SFC 编译在项目构建期间执行，运行时只存在编译后的代码．\\\n（您需要在开发环境中设置 Vite 或 webpack 等打包器．）\n\n```vue\n<!-- App.vue -->\n<script>\nexport default {}\n</script>\n\n<template>\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert(\"hello\")\">click me!</button>\n</template>\n```\n\n```ts\nimport App from 'App.vue'\nconst app = createApp(App)\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\n需要注意的重要一点是，两个编译器共享公共处理．\\\n这个公共部分的源代码在 `compiler-core` 目录中实现．\\\n运行时编译器和 SFC 编译器分别在 `compiler-dom` 和 `compiler-sfc` 目录中实现．\\\n请再次查看这个图表．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n## 继续实现\n\n我们跳得有点快，但让我们继续实现．\\\n虽然我想实现 `packages/index.ts`，但有一些准备工作要做，所以让我们先做那个．\\\n准备工作是在 `packages/runtime-core/component.ts` 中实现一个变量来保存编译器本身，以及一个注册函数．\n\n`packages/runtime-core/component.ts`\n\n```ts\ntype CompileFunction = (template: string) => InternalRenderFunction\nlet compile: CompileFunction | undefined\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile\n}\n```\n\n现在，让我们在 `packages/index.ts` 中生成函数并注册它．\n\n```ts\nimport { compile } from './compiler-dom'\nimport { InternalRenderFunction, registerRuntimeCompiler } from './runtime-core'\nimport * as runtimeDom from './runtime-dom'\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template)\n  return new Function('ChibiVue', code)(runtimeDom)\n}\n\nregisterRuntimeCompiler(compileToFunction)\n\nexport * from './runtime-core'\nexport * from './runtime-dom'\nexport * from './reactivity'\n```\n\n※ 不要忘记从 `runtime-dom` 导出 `h` 函数，因为它需要包含在 `runtimeDom` 中．\n\n```ts\nexport { h } from '../runtime-core'\n```\n\n现在编译器已注册，让我们实际执行编译．\\\n由于组件选项类型中需要模板，让我们现在添加模板．\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function\n  render?: Function\n  template?: string // 添加\n}\n```\n\n现在，让我们编译重要部分．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  // ----------------------- 从这里\n  const { props } = instance.vnode\n  initProps(instance, props)\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n  // ----------------------- 到这里\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\n我们将在 `packages/runtime-core/component.ts` 中提取上述部分．\n\n`packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n}\n```\n\n`packages/runtime-core/renderer.ts`\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // prettier-ignore\n  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n  setupComponent(instance)\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\n现在，让我们在 `setupComponent` 函数内部执行编译．\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n\n  // ------------------------ 这里\n  if (compile && !component.render) {\n    const template = component.template ?? ''\n    if (template) {\n      instance.render = compile(template)\n    }\n  }\n}\n```\n\n现在，我们应该能够使用 `template` 选项编译简单的 HTML．\\\n让我们在游乐场中试试！\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n![Simple template compiler output before cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-before.png)\n\n看起来工作正常．\\\n让我们尝试做一些更改，看看它们是否得到反映．\n\n```ts\nconst app = createApp({\n  template: `<b class=\"hello\" style=\"color: red;\">Hello World!!</b>`,\n})\napp.mount('#app')\n```\n\n![Simple template compiler output after cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-after.png)\n\n看起来实现正确！\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/070-more-complex-parser.md",
    "content": "# 我想编写更复杂的 HTML\n\n## 我想编写更复杂的 HTML\n\n在当前状态下，我只能表达标签的名称和属性，以及文本的内容．\\\n因此，我想能够在模板中编写更复杂的 HTML．\\\n具体来说，我想能够编译这样的模板：\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n\n  `,\n})\napp.mount('#app')\n```\n\n但是，用正则表达式解析如此复杂的 HTML 是困难的．\\\n所以，从这里开始，我将认真实现一个解析器．\n\n## AST 的介绍\n\n为了实现一个成熟的编译器，我将引入一个叫做 AST（抽象语法树）的东西．\\\nAST 代表抽象语法树，顾名思义，它是表示语法的树结构的数据表示．\\\n这是在实现各种编译器时出现的概念，不仅仅是 Vue.js．\\\n在许多情况下（在语言处理系统中），\"解析\"指的是将其转换为这种称为 AST 的表示．\\\nAST 的定义由每种语言定义．\\\n例如，您熟悉的 JavaScript 由称为 [estree](https://github.com/estree/estree) 的 AST 表示，源代码字符串根据此定义进行解析．\n\n我试图以一种酷的方式解释它，但在图像方面，它只是我们迄今为止实现的 parse 函数返回类型的正式定义．\\\n目前，parse 函数的返回值如下：\n\n```ts\ntype ParseResult = {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}\n```\n\n让我们扩展这个并定义它，以便可以执行更复杂的表达式．\n\n创建一个新文件 `~/packages/compiler-core/ast.ts`．\\\n我将在编写代码时解释，因为它有点长．\n\n```ts\n// 这表示节点的类型。\n// 应该注意的是，这里的 Node 不是指 HTML Node，而是指这个模板编译器处理的粒度。\n// 所以，不仅 Element 和 Text，Attribute 也被视为一个 Node。\n// 这与 Vue.js 的设计一致，在将来实现指令时会很有用。\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  ATTRIBUTE,\n}\n\n// 所有 Node 都有 type 和 loc。\n// loc 代表位置，保存关于这个 Node 在源代码（模板字符串）中对应位置的信息。\n// （例如，哪一行和行上的哪个位置）\nexport interface Node {\n  type: NodeTypes\n  loc: SourceLocation\n}\n\n// Element 的 Node。\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string // 例如 \"div\"\n  props: Array<AttributeNode> // 例如 { name: \"class\", value: { content: \"container\" } }\n  children: TemplateChildNode[]\n  isSelfClosing: boolean // 例如 <img /> -> true\n}\n\n// ElementNode 拥有的 Attribute。\n// 它可以表达为只是 Record<string, string>，\n// 但它被定义为像 Vue 一样具有 name(string) 和 value(TextNode)。\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE\n  name: string\n  value: TextNode | undefined\n}\n\nexport type TemplateChildNode = ElementNode | TextNode\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT\n  content: string\n}\n\n// 关于位置的信息。\n// Node 有这个信息。\n// start 和 end 包含位置信息。\n// source 包含实际代码（字符串）。\nexport interface SourceLocation {\n  start: Position\n  end: Position\n  source: string\n}\n\nexport interface Position {\n  offset: number // 从文件开始\n  line: number\n  column: number\n}\n```\n\n这是我们这次将要处理的 AST．\\\n在 parse 函数中，我们将实现将模板字符串转换为这个 AST．\n\n## 成熟解析器的实现\n\n::: warning\n在 2023 年 11 月下旬，在 [vuejs/core#9674](https://github.com/vuejs/core/pull/9674) 中进行了性能改进的重大重写．  \n这些更改在 2023 年 12 月下旬作为 [Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) 发布．  \n请注意，这本在线书籍参考的是此重写之前的实现．  \n我们计划在适当的时机相应地更新这本在线书籍．\n:::\n\n在 `~/packages/compiler-core/parse.ts` 中实现它．\n即使我说它是成熟的，你也不必太紧张．\\\n基本上，你所做的就是在读取字符串时生成 AST，并使用分支和循环．\\\n源代码会有点长，但我认为在代码库中解释会更容易理解．所以让我们这样进行．\\\n请通过阅读源代码来尝试理解细节．\n\n删除您迄今为止实现的 baseParse 的内容，并按如下方式更改返回类型：\n\n```ts\nimport { TemplateChildNode } from './ast'\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  // TODO:\n  return { children: [] }\n}\n```\n\n## Context\n\n首先，让我们实现解析期间使用的状态．\n\\我们将其命名为 `ParserContext`，并在解析期间在这里收集必要的信息．\\\n最终，我认为它也会保存解析器配置选项等．\n\n```ts\nexport interface ParserContext {\n  // 原始模板字符串\n  readonly originalSource: string\n\n  source: string\n\n  // 此解析器正在读取的当前位置\n  offset: number\n  line: number\n  column: number\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  }\n}\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content) // 创建上下文\n\n  // TODO:\n  return { children: [] }\n}\n```\n\n## parseChildren\n\n在顺序方面，解析按如下方式进行：(parseChildren) -> (parseElement 或 parseText)．\n\n虽然有点长，但让我们从 parseChildren 的实现开始．\\\n解释将在源代码的注释中完成．\n\n```ts\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content)\n  const children = parseChildren(context, []) // 解析子节点\n  return { children: children }\n}\n\nfunction parseChildren(\n  context: ParserContext,\n\n  // 由于 HTML 具有递归结构，我们将祖先元素保持为堆栈，并在每次嵌套到子元素中时推送它们。\n  // 当找到结束标签时，parseChildren 结束并弹出祖先。\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (s[0] === '<') {\n      // 如果 s 以 \"<\" 开头且下一个字符是字母，则将其解析为元素。\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors) // TODO: 稍后实现这个。\n      }\n    }\n\n    if (!node) {\n      // 如果不匹配上述条件，则将其解析为 TextNode。\n      node = parseText(context) // TODO: 稍后实现这个。\n    }\n\n    pushNode(nodes, node)\n  }\n\n  return nodes\n}\n\n// 确定解析子元素的 while 循环结束的函数\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // 如果 s 以 \"</\" 开头且祖先的标签名跟随，它确定是否有闭合标签（parseChildren 是否应该结束）。\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString)\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  // 如果 Text 类型的节点是连续的，它们会被合并。\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes)\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content\n      return\n    }\n  }\n\n  nodes.push(node)\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1]\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, '</') &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || '>')\n  )\n}\n```\n\n接下来，让我们实现 parseElement 和 parseText．\n\n::: tip 关于 isEnd 循环\n在 isEnd 中，有一个循环过程，使用 startsWithEndTagOpen 检查 's' 是否以 ancestors 数组中每个元素的闭合标签开头．\n\n```ts\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // 如果 s 以 </ 开头且祖先的标签名跟随，它确定是否有闭合标签（parseChildren 是否应该结束）。\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n```\n\n但是，如果您需要检查 's' 是否以闭合标签开头，只检查 ancestors 中的最后一个元素应该就足够了．\\\n虽然这部分代码在解析器的最近重写中被消除了，但将 Vue 3.3 代码修改为只检查 ancestors 中的最后一个元素仍然会导致所有正面测试成功通过．\n:::\n\n## parseText\n\n首先，让我们从简单的 parseText 开始．\\\n它有点长，因为它还实现了一些不仅在 parseText 中使用，而且在其他函数中也使用的实用程序．\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  // 读取直到 \"<\"（无论它是开始还是结束标签），并根据读取了多少字符计算 Text 数据结束点的索引。\n  const endToken = '<'\n  let endIndex = context.source.length\n  const index = context.source.indexOf(endToken, 1)\n  if (index !== -1 && endIndex > index) {\n    endIndex = index\n  }\n\n  const start = getCursor(context) // 用于 loc\n\n  // 根据 endIndex 的信息解析 Text 数据。\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n\n// 根据内容和长度提取文本。\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length)\n  advanceBy(context, length)\n  return rawText\n}\n\n// -------------------- 以下是实用程序（也在 parseElement 等中使用） --------------------\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context\n  advancePositionWithMutation(context, source, numberOfCharacters)\n  context.source = source.slice(numberOfCharacters)\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\n// 虽然有点长，但它只是计算位置。\n// 它破坏性地更新作为参数接收的 pos 对象。\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0\n  let lastNewLinePos = -1\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++\n      lastNewLinePos = i\n    }\n  }\n\n  pos.offset += numberOfCharacters\n  pos.line += linesCount\n  pos.column =\n    lastNewLinePos === -1\n      ? pos.column + numberOfCharacters\n      : numberOfCharacters - lastNewLinePos\n\n  return pos\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context\n  return { column, line, offset }\n}\n\nfunction getSelection(\n  context: ParserContext,\n  start: Position,\n  end?: Position,\n): SourceLocation {\n  end = end || getCursor(context)\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  }\n}\n```\n\n## parseElement\n\n接下来是元素的解析．\n元素的解析主要包括解析开始标签，解析子节点和解析结束标签．\\\n开始标签的解析进一步分为标签名和属性．\\\n让我们首先创建一个框架来解析开始标签的前半部分，子节点和结束标签．\n\n```ts\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // 开始标签。\n  const element = parseTag(context, TagType.Start) // TODO:\n\n  // 如果它是像 <img /> 这样的自闭合元素，我们在这里结束（因为没有子元素或结束标签）。\n  if (element.isSelfClosing) {\n    return element\n  }\n\n  // 子元素。\n  ancestors.push(element)\n  const children = parseChildren(context, ancestors)\n  ancestors.pop()\n\n  element.children = children\n\n  // 结束标签。\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End) // TODO:\n  }\n\n  return element\n}\n```\n\n这里没有什么特别困难的．\\\n`parseChildren` 函数是递归的（因为 `parseElement` 被 `parseChildren` 调用）．\\\n我们在前后操作 `ancestors` 数据结构作为堆栈．\n\n让我们实现 `parseTag`．\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // 标签打开。\n  const start = getCursor(context)\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!\n  const tag = match[1]\n\n  advanceBy(context, match[0].length)\n  advanceSpaces(context)\n\n  // 属性。\n  let props = parseAttributes(context, type)\n\n  // 标签关闭。\n  let isSelfClosing = false\n\n  // 如果下一个字符是 \"/>\"，它是一个自闭合标签。\n  isSelfClosing = startsWith(context.source, '/>')\n  advanceBy(context, isSelfClosing ? 2 : 1)\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  }\n}\n\n// 解析整个属性（多个属性）。\n// 例如 `id=\"app\" class=\"container\" style=\"color: red\"`\nfunction parseAttributes(\n  context: ParserContext,\n  type: TagType,\n): AttributeNode[] {\n  const props = []\n  const attributeNames = new Set<string>()\n\n  // 继续读取直到标签结束。\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, '>') &&\n    !startsWith(context.source, '/>')\n  ) {\n    const attr = parseAttribute(context, attributeNames)\n\n    if (type === TagType.Start) {\n      props.push(attr)\n    }\n\n    advanceSpaces(context) // 跳过空格。\n  }\n\n  return props\n}\n\ntype AttributeValue =\n  | {\n      content: string\n      loc: SourceLocation\n    }\n  | undefined\n\n// 解析单个属性。\n// 例如 `id=\"app\"`\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode {\n  // 名称。\n  const start = getCursor(context)\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!\n  const name = match[0]\n\n  nameSet.add(name)\n\n  advanceBy(context, name.length)\n\n  // 值\n  let value: AttributeValue = undefined\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context)\n    advanceBy(context, 1)\n    advanceSpaces(context)\n    value = parseAttributeValue(context)\n  }\n\n  const loc = getSelection(context, start)\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  }\n}\n\n// 解析属性的值。\n// 此实现允许解析值，无论它们是单引号还是双引号。\n// 它只是提取引号中包含的值。\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context)\n  let content: string\n\n  const quote = context.source[0]\n  const isQuoted = quote === `\"` || quote === `'`\n  if (isQuoted) {\n    // 引用值。\n    advanceBy(context, 1)\n\n    const endIndex = context.source.indexOf(quote)\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length)\n    } else {\n      content = parseTextData(context, endIndex)\n      advanceBy(context, 1)\n    }\n  } else {\n    // 未引用\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source)\n    if (!match) {\n      return undefined\n    }\n    content = parseTextData(context, match[0].length)\n  }\n\n  return { content, loc: getSelection(context, start) }\n}\n```\n\n## 完成解析器实现后\n\n我写了很多代码，比平时多．（最多只有大约 300 行）\\\n我认为在这里阅读实现比用特殊词汇解释更好，所以请反复阅读．\\\n虽然我写了很多，但基本上它是通过读取字符串推进分析的直接任务，没有特别困难的技术．\n\n到现在，您应该能够生成 AST．让我们检查解析是否正常工作．\\\n但是，由于 codegen 部分尚未实现，我们这次将输出到控制台进行确认．\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\napp.mount('#app')\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim()) // 修剪模板\n  console.log(\n    '🚀 ~ file: compile.ts:6 ~ baseCompile ~ parseResult:',\n    parseResult,\n  )\n\n  // TODO: codegen\n  // const code = generate(parseResult);\n  // return code;\n  return ''\n}\n```\n\n屏幕不会显示任何内容，但让我们检查控制台．\n\n![AST output for complex HTML](/figures/10-minimum-example/more-complex-parser/complex-html-ast.png)\n\n看起来解析进展顺利．\\\n现在，让我们基于生成的 AST 继续实现 codegen．\n\n## 基于 AST 生成渲染函数\n\n现在我们已经实现了一个成熟的解析器，让我们创建一个可以应用于它的代码生成器．\\\n但是，在这一点上，不需要复杂的实现．\\\n我将首先向您展示代码．\n\n```ts\nimport { ElementNode, NodeTypes, TemplateChildNode, TextNode } from './ast'\n\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render() {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node)\n    case NodeTypes.TEXT:\n      return genText(node)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) => `${name}: \"${value?.content}\"`)\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``\n}\n```\n\n使用上述代码，您可以创建有效的东西．\\\n取消注释解析器章节中被注释掉的部分并检查实际操作．\\\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult)\n  return code\n}\n```\n\nplayground\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\n![Rendered template result in the browser](/figures/10-minimum-example/more-complex-parser/render-template-result.png)\n\n怎么样？看起来我们可以很好地渲染屏幕．\n\n让我们为屏幕添加一些动作．\\\n由于我们还没有实现模板绑定，我们将直接操作 DOM．\n\n```ts\nexport type ComponentOptions = {\n  // .\n  // .\n  // .\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | void // 也允许 void\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // 使用 Promise.resolve 延迟处理，以便在挂载后可以执行 DOM 操作\n    Promise.resolve().then(() => {\n      const btn = document.getElementById('btn')\n      btn &&\n        btn.addEventListener('click', () => {\n          const h2 = document.getElementById('hello')\n          h2 && (h2.textContent += '!')\n        })\n    })\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2 id=\"hello\">Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button id=\"btn\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\n让我们确保它正常工作．\\\n怎么样？虽然功能有限，但它越来越接近通常的 Vue 开发者接口．\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler2)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/080-template-binding.md",
    "content": "# 数据绑定\n\n## 想要绑定到模板\n\n目前，我们直接操作 DOM，因此无法利用响应式系统或虚拟 DOM．\\\n实际上，我们希望在模板部分编写事件处理程序和文本内容．这就是声明式 UI 的乐趣所在．\\\n我们的目标是实现如下的开发者接口．\n\n```ts\nimport { createApp, reactive, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render() {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', {}, `message: ${this.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', {}, [h('b', {}, 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onclick: this.changeMessage }, 'click me!'),\n      h(\n        'style',\n        {},\n        `\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      `,\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n现在，我想能够在模板中处理从 `setup` 函数返回的值．\\\n从现在开始，我将把这称为\"模板绑定\"或简称\"绑定\"．\\\n我将实现绑定，但在实现事件处理程序和 mustache 语法之前，有几件事我想做．\n\n我提到了从 `setup` 返回的值，但目前 `setup` 的返回值要么是 `undefined`，要么是一个函数（渲染函数）．\\\n作为实现绑定的准备，我需要修改它，使 `setup` 可以返回状态和其他值，并且这些值可以作为组件数据存储．\n\n```ts\nexport type ComponentOptions = {\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void\n  // 允许返回 Record<string, unknown>\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupState: Data // 将 setup 的结果作为对象存储在这里\n}\n```\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n\n    // 根据 setupResult 的类型进行分支\n    if (typeof setupResult === 'function') {\n      instance.render = setupResult\n    } else if (typeof setupResult === 'object' && setupResult !== null) {\n      instance.setupState = setupResult\n    } else {\n      // do nothing\n    }\n  }\n  // .\n  // .\n  // .\n}\n```\n\n从现在开始，我将把在 `setup` 中定义的数据称为 `setupState`．\n\n现在，在实现编译器之前，让我们思考如何将 `setupState` 绑定到模板．\\\n之前，我们这样绑定 `setupState`：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return () => h('div', {}, [state.message])\n  },\n})\n```\n\n嗯，这实际上不是真正的绑定，而是渲染函数简单地形成闭包并引用变量．\\\n然而，这次，由于 setup 选项和渲染函数在概念上是不同的，我们需要找到一种方法将 setup 数据传递给渲染函数．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  // 这将被转换为渲染函数\n  template: '<div>{{ state.message }}</div>',\n})\n```\n\n`template` 使用 `h` 函数编译为渲染函数并分配给 `instance.render`．\\\n因此，它等价于以下代码：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render() {\n    return h('div', {}, [state.message])\n  },\n})\n```\n\n自然地，变量 `state` 在渲染函数内部没有定义．\\\n现在，我们如何引用 `state` 变量？\n\n## 使用 `with` 语句\n\n总之，我们可以使用 `with` 语句来实现所需的结果：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render(ctx) {\n    with (ctx) {\n      return h('div', {}, [state.message])\n    }\n  },\n})\n```\n\n我相信有很多人不熟悉 `with` 语句．\n\n这是有充分理由的，这个功能已被弃用．\n\n根据 MDN：\n\n> 虽然仍然被一些浏览器支持，但它已从 Web 标准中弃用。但是，它可能仍在用于各种目的，例如与遗留代码的兼容性。避免使用它，如果可能的话更新现有代码。\n\n因此，建议避免使用它．\n\n我们不知道 Vue.js 的实现将来会如何变化，但由于 Vue.js 3 使用 `with` 语句，我们将在此实现中使用它．\n\n稍微说一下，Vue.js 中并非所有内容都使用 `with` 语句实现．\\\n在处理单文件组件（SFC）中的模板时，它是在不使用 `with` 语句的情况下实现的．\\\n我们将在后面的章节中介绍这一点，但现在，让我们考虑使用 `with` 来实现它．\n\n---\n\n现在，让我们回顾一下 `with` 语句的行为．\n`with` 语句扩展语句的作用域链．\n\n它的行为如下：\n\n```ts\nconst obj = { a: 1, b: 2 }\n\nwith (obj) {\n  console.log(a, b) // 1, 2\n}\n```\n\n通过将包含 `state` 的父对象作为参数传递给 `with`，我们可以引用 `state` 变量．\n\n在这种情况下，我们将把 `setupState` 视为父对象．\\\n实际上，不仅是 `setupState`，来自 `props` 的数据和在 Options API 中定义的数据也应该是可访问的．\\\n但是，现在，我们只考虑使用来自 `setupState` 的数据．\n（我们将在后面的部分中介绍这部分的实现，因为它不是最小实现的一部分．）\n\n总结我们这次想要实现的内容，我们想要编译以下模板：\n\n```html\n<div>\n  <p>{{ state.message }}</p>\n  <button @click=\"changeMessage\">click me</button>\n</div>\n```\n\n转换为以下函数：\n\n```ts\n_ctx => {\n  with (_ctx) {\n    return h('div', {}, [\n      h('p', {}, [state.message]),\n      h('button', { onClick: changeMessage }, ['click me']),\n    ])\n  }\n}\n```\n\n并将 `setupState` 传递给这个函数：\n\n```ts\nconst setupState = setup()\nrender(setupState)\n```\n\n## 实现 Mustache 语法\n\n首先，让我们实现 Mustache 语法．\\\n像往常一样，我们将考虑 AST，实现解析器，然后实现代码生成器．\\\n目前，作为 AST 一部分定义的唯一节点是 `Element`，`Text` 和 `Attribute`．\\\n由于我们想要定义 Mustache 语法，直觉上有一个叫做 `Mustache` 的 AST 是有意义的．\\\n为此，我们将使用 `Interpolation` 节点．\\\nInterpolation 有\"插值\"或\"插入\"等含义．\\\n因此，我们这次将处理的 AST 将如下所示：\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION, // 添加\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode // 添加 InterpolationNode\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // Mustache 内部编写的内容（在这种情况下，在 setup 中定义的单个变量名将放在这里）\n}\n```\n\n现在 AST 已经实现，让我们继续实现解析器．\\\n当我们找到字符串 <span v-pre>`{{`</span> 时，我们将把它解析为 `Interpolation`．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[]\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n\n    if (startsWith(s, \"{{\")) { // 这里\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n    // .\n    // .\n    //\n    }\n```\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  const [open, close] = ['{{', '}}']\n  const closeIndex = context.source.indexOf(close, open.length)\n  if (closeIndex === -1) return undefined\n\n  const start = getCursor(context)\n  advanceBy(context, open.length)\n\n  const innerStart = getCursor(context)\n  const innerEnd = getCursor(context)\n  const rawContentLength = closeIndex - open.length\n  const rawContent = context.source.slice(0, rawContentLength)\n  const preTrimContent = parseTextData(context, rawContentLength)\n\n  const content = preTrimContent.trim()\n\n  const startOffset = preTrimContent.indexOf(content)\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset)\n  }\n  const endOffset =\n    rawContentLength - (preTrimContent.length - content.length - startOffset)\n  advancePositionWithMutation(innerEnd, rawContent, endOffset)\n  advanceBy(context, close.length)\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n有些情况下 <span v-pre>`{{`</span> 出现在文本中，所以我们将对 `parseText` 进行一些修改．\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = ['<', '{{'] // 如果 <span v-pre>`{{`</span> 出现，parseText 结束\n\n  let endIndex = context.source.length\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1)\n    if (index !== -1 && endIndex > index) {\n      endIndex = index\n    }\n  }\n\n  const start = getCursor(context)\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n对于那些到目前为止已经实现了解析器的人来说，应该没有特别困难的部分．\\\n它只是搜索 <span v-pre>`{{`</span> 并读取直到 <span v-pre>`}}`</span> 出现，生成 AST．\\\n如果没有找到 <span v-pre>`}}`</span>，它返回 undefined 并在 parseText 的分支中将其解析为文本．\n\n让我们输出到控制台或其他地方，以确保解析正常工作．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Interpolation AST output](/figures/10-minimum-example/template-binding/parse-interpolation-ast.png)\n\n看起来不错！\n\n现在让我们基于这个 AST 实现绑定．\\\n用 with 语句包装渲染函数的内容．\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    // .\n    // .\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node)\n    // .\n    // .\n  }\n}\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`\n}\n```\n\n最后，在执行渲染函数时，将 `setupState` 作为参数传递．\\\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild // 接受 ctx 作为参数\n}\n```\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render, setupState } = instance\n    if (!instance.isMounted) {\n      // .\n      // .\n      // .\n      const subTree = (instance.subTree = normalizeVNode(render(setupState))) // 传递 setupState\n      // .\n      // .\n      // .\n    } else {\n      // .\n      // .\n      // .\n      const nextTree = normalizeVNode(render(setupState)) // 传递 setupState\n      // .\n      // .\n      // .\n    }\n  }\n}\n```\n\n如果你已经走到这一步，你应该能够渲染了．让我们检查一下！\n\n![Rendered interpolation result in the browser](/figures/10-minimum-example/template-binding/render-interpolation-result.png)\n\n这完成了第一个绑定！\n\n## 第一个指令\n\n接下来是事件处理程序．\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) =>\n      // 如果是 @click，将 props 名称转换为 onClick\n      name === '@click'\n        ? `onClick: ${value?.content}`\n        : `${name}: \"${value?.content}\"`,\n    )\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n```\n\n让我们检查操作．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n你做到了！做得好！完成了！\n\n我想这样说，但实现还不够干净，所以我想稍微重构一下．\\\n由于 `@click` 被归类为\"指令\"名称，很容易想象将来实现 `v-bind` 和 `v-model`．\\\n所以让我们在 AST 中将其表示为 `DIRECTIVE` 并将其与简单的 `ATTRIBUTE` 区分开来．\n\n像往常一样，让我们按照 AST -> parse -> codegen 的顺序实现它．\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE, // 添加\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode> // props 是 AttributeNode 和 DirectiveNode 联合的数组\n  // .\n  // .\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  // 表示 `v-name:arg=\"exp\"` 的格式。\n  // 例如，对于 `v-on:click=\"increment\"`，它将是 { name: \"on\", arg: \"click\", exp=\"increment\" }\n  name: string\n  arg: string\n  exp: string\n}\n```\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>\n): AttributeNode | DirectiveNode {\n  // 名称。\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // 值\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // --------------------------------------------------- 从这里\n  // 指令\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match =\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name\n      )!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n  // --------------------------------------------------- 到这里\n  // .\n  // .\n  // .\n```\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop))\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`\n        default:\n          // TODO: 其他指令\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n```\n\n现在，让我们在游乐场中检查操作．\\\n你应该能够处理不仅 `@click`，还有 `v-on:click` 和其他事件．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Compiled directive result in the browser](/figures/10-minimum-example/template-binding/compile-directives-result.png)\n\n你做到了．\\\n我们越来越接近 Vue 了！\\\n有了这个，小模板的实现就完成了．做得好．\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler3)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/090-prerequisite-knowledge-for-the-sfc.md",
    "content": "## 周边知识\n\n## SFC 是如何实现的？\n\n现在，让我们最终开始支持单文件组件（SFC）．\\\n那么，我们应该如何支持它呢？SFC 就像模板一样，在开发期间使用，在运行时不存在．\\\n对于那些已经完成模板开发的人来说，我认为这只是如何编译它的简单问题．\n\n你只需要将以下 SFC 代码：\n\n```vue\n<script>\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>message: {{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n转换为以下 JS 代码：\n\n```ts\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render(_ctx) {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', `message: ${_ctx.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', [h('b', 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onClick: _ctx.changeMessage }, 'click me!'),\n    ])\n  },\n}\n```\n\n你可能会想知道样式！但现在，让我们忘记这一点，专注于模板和脚本．\\\n我们不会在最小示例中涵盖 `script setup`．\n\n## 我们应该何时以及如何编译？\n\n总之，\"我们在构建工具解析依赖项时编译\"．\n在大多数情况下，SFC 从其他文件导入和使用．\n此时，我们编写一个插件，在解析 `.vue` 文件时编译它并将结果绑定到应用程序．\n\n```ts\nimport App from './App.vue' // 导入 App.vue 时编译\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\n有各种构建工具，但这次让我们尝试为 Vite 编写一个插件．\n\n由于可能很少有人从未编写过 Vite 插件，让我们首先通过一个简单的示例代码熟悉插件实现．让我们现在创建一个简单的 Vue 项目．\n\n```sh\npwd # ~\npnpm dlx create-vite\n## ✔ Project name: … plugin-sample\n## ✔ Select a framework: › Vue\n## ✔ Select a variant: › TypeScript\n\ncd plugin-sample\nni\n```\n\n让我们看看创建项目的 vite.config.ts 文件．\n\n```ts\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue()],\n})\n```\n\n你可以看到它将 `@vitejs/plugin-vue` 添加到插件中．\n实际上，当使用 Vite 创建 Vue 项目时，由于这个插件，可以使用 SFC．\n这个插件根据 Vite 插件 API 实现 SFC 编译器，并将 Vue 文件编译为 JS 文件．\n让我们尝试在这个项目中创建一个简单的插件．\n\n```ts\nimport { defineConfig, Plugin } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue(), myPlugin()],\n})\n\nfunction myPlugin(): Plugin {\n  return {\n    name: 'vite:my-plugin',\n\n    transform(code, id) {\n      if (id.endsWith('.sample.js')) {\n        let result = ''\n\n        for (let i = 0; i < 100; i++) {\n          result += `console.log(\"HelloWorld from plugin! (${i})\");\\n`\n        }\n\n        result += code\n\n        return { code: result }\n      }\n    },\n  }\n}\n```\n\n我创建了一个名为 `myPlugin` 的插件．\\\n由于它很简单，我认为很多人不用解释就能理解，但我还是会解释一下以防万一．\n\n插件符合 Vite 要求的格式．\\\n有各种选项，但由于这是一个简单的示例，我只使用了 `transform` 选项．\\\n我建议查看官方文档和其他资源以获取更多信息：https://vite.dev/guide/api-plugin\n\n在 `transform` 函数中，你可以接收 `code` 和 `id`．\\\n你可以将 `code` 视为文件的内容，将 `id` 视为文件名．\\\n作为返回值，你将结果放在 `code` 属性中．\n你可以根据 `id` 为每种文件类型编写不同的处理，或修改 `code` 来重写文件的内容．\\\n在这种情况下，我为以 `*.sample.js` 结尾的文件在文件内容的开头添加了 100 个控制台日志．\\\n现在，让我们实现一个示例 `plugin.sample.js` 并检查它．\n\n```sh\npwd # ~/plugin-sample\ntouch src/plugin.sample.js\n```\n\n`~/plugin-sample/src/plugin.sample.js`\n\n```ts\nfunction fizzbuzz(n) {\n  for (let i = 1; i <= n; i++) {\n    i % 3 === 0 && i % 5 === 0\n      ? console.log('fizzbuzz')\n      : i % 3 === 0\n        ? console.log('fizz')\n        : i % 5 === 0\n          ? console.log('buzz')\n          : console.log(i)\n  }\n}\n\nfizzbuzz(Math.floor(Math.random() * 100) + 1)\n```\n\n`~/plugin-sample/src/main.ts`\n\n```ts\nimport { createApp } from 'vue'\nimport './style.css'\nimport App from './App.vue'\nimport './plugin.sample.js' // 添加\n\ncreateApp(App).mount('#app')\n```\n\n让我们在浏览器中检查它．\n\n```sh\npwd # ~/plugin-sample\npnpm dev\n```\n\n![Sample Vite plugin console output](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-console.png)\n\n![Sample Vite plugin transformed source](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-source.png)\n\n你可以看到源代码已经被正确修改了．\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/091-parse-sfc.md",
    "content": "# 实现 SFC 解析器\n\n## 准备工作\n\n虽然这是我们之前创建的示例插件，但让我们删除它，因为它不再需要了．\n\n```sh\npwd # ~\nrm -rf ./plugin-sample\n```\n\n另外，为了创建 Vite 插件，请安装主要的 Vite 包．\n\n```sh\npwd # ~\npnpm add vite\n```\n\n这是插件的主要部分，但由于这原本超出了 vuejs/core 的范围，我们将在 `packages` 目录中创建一个名为 `@extensions` 的目录并在那里实现它．\n\n```sh\npwd # ~\nmkdir -p packages/@extensions/vite-plugin-chibivue\ntouch packages/@extensions/vite-plugin-chibivue/index.ts\n```\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      return { code }\n    },\n  }\n}\n```\n\n现在，让我们实现 SFC 编译器．\\\n但是，没有任何实质内容可能很难想象，所以让我们实现一个游乐场并在运行时进行．\\\n我们将创建一个简单的 SFC 并加载它．\n\n```sh\npwd # ~\ntouch examples/playground/src/App.vue\n```\n\n`examples/playground/src/App.vue`\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n`playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n`playground/vite.config.js`\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nimport chibivue from '../../packages/@extensions/vite-plugin-chibivue'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n  plugins: [chibivue()],\n})\n```\n\n让我们尝试在这种状态下启动．\n\n![Vite error before the SFC plugin is implemented](/figures/10-minimum-example/parse-sfc/vite-error.png)\n\n当然，这会导致错误．做得好（？）．\n\n## 解决错误\n\n让我们暂时解决错误．我们不会立即追求完美．\\\n首先，让我们将 `transform` 的目标限制为 \"\\*.vue\"．\\\n我们可以像在示例中那样使用 `id` 编写分支语句，但由于 Vite 提供了一个名为 `createFilter` 的函数，让我们使用它创建一个过滤器．\\\n（这没有特别的原因．）\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\n我们创建了一个过滤器，如果是 Vue 文件，则将文件内容转换为 `export default {}`．\\\n错误应该消失，屏幕应该不显示任何内容．\n\n## 在 compiler-sfc 上实现解析器\n\n现在，这只是一个临时解决方案，所以让我们实现一个合适的解决方案．\\\nvite-plugin 的作用是使用 Vite 启用转换，所以解析和编译在主 Vue 包中．\\\n那就是 `compiler-sfc` 目录．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\nSFC 编译器对于 Vite 和 Webpack 都是相同的．\\\n核心实现在 `compiler-sfc` 中．\n\n让我们创建 `compiler-sfc`．\n\n```sh\npwd # ~\nmkdir packages/compiler-sfc\ntouch packages/compiler-sfc/index.ts\n```\n\n在 SFC 编译中，SFC 由一个名为 `SFCDescriptor` 的对象表示．\n\n```sh\ntouch packages/compiler-sfc/parse.ts\n```\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { SourceLocation } from '../compiler-core'\n\nexport interface SFCDescriptor {\n  id: string\n  filename: string\n  source: string\n  template: SFCTemplateBlock | null\n  script: SFCScriptBlock | null\n  styles: SFCStyleBlock[]\n}\n\nexport interface SFCBlock {\n  type: string\n  content: string\n  loc: SourceLocation\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: 'template'\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: 'script'\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: 'style'\n}\n```\n\n嗯，没有什么特别困难的．\\\n它只是一个表示 SFC 信息的对象．\n\n在 `packages/compiler-sfc/parse.ts` 中，我们将把 SFC 文件（字符串）解析为 `SFCDescriptor`．\\\n你们中的一些人可能在想，\"什么？你在模板解析器上如此努力工作，现在你要创建另一个解析器...？这很麻烦．\"但不要担心．\\\n我们在这里要实现的解析器并不是什么大事．那是因为我们只是通过结合我们迄今为止创建的内容来分离模板，脚本和样式．\n\n首先，作为准备，导出我们之前创建的模板解析器．\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nimport { baseCompile, baseParse } from '../compiler-core'\n\nexport function compile(template: string) {\n  return baseCompile(template)\n}\n\n// 导出解析器\nexport function parse(template: string) {\n  return baseParse(template)\n}\n```\n\n在 compiler-sfc 端保留这些接口．\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/compileTemplate.ts\n```\n\n`~/packages/compiler-sfc/compileTemplate.ts`\n\n```ts\nimport { TemplateChildNode } from '../compiler-core'\n\nexport interface TemplateCompiler {\n  compile(template: string): string\n  parse(template: string): { children: TemplateChildNode[] }\n}\n```\n\n然后，只需实现解析器．\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { ElementNode, NodeTypes, SourceLocation } from '../compiler-core'\nimport * as CompilerDOM from '../compiler-dom'\nimport { TemplateCompiler } from './compileTemplate'\n\nexport interface SFCParseOptions {\n  filename?: string\n  sourceRoot?: string\n  compiler?: TemplateCompiler\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor\n}\n\nexport const DEFAULT_FILENAME = 'anonymous.vue'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  }\n\n  const ast = compiler.parse(source)\n  ast.children.forEach(node => {\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    switch (node.tag) {\n      case 'template': {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock\n        break\n      }\n      case 'script': {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock\n        descriptor.script = scriptBlock\n        break\n      }\n      case 'style': {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock)\n        break\n      }\n      default: {\n        break\n      }\n    }\n  })\n\n  return { descriptor }\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag\n\n  let { start, end } = node.loc\n  start = node.children[0].loc.start\n  end = node.children[node.children.length - 1].loc.end\n  const content = source.slice(start.offset, end.offset)\n\n  const loc = { source: content, start, end }\n  const block: SFCBlock = { type, content, loc }\n\n  return block\n}\n```\n\n我认为对于到目前为止已经实现了解析器的每个人来说都很容易．让我们在插件中实际解析 SFC．\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport { parse } from '../../compiler-sfc'\n\nexport default function vitePluginChibivue(): Plugin {\n  //.\n  //.\n  //.\n  return {\n    //.\n    //.\n    //.\n    transform(code, id) {\n      if (!filter(id)) return\n      const { descriptor } = parse(code, { filename: id })\n      console.log(\n        '🚀 ~ file: index.ts:14 ~ transform ~ descriptor:',\n        descriptor,\n      )\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\n这段代码在 Vite 运行的进程中运行，这意味着它在 Node 中执行，所以我认为控制台输出会显示在终端中．\n\n![SFC descriptor before parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-before.png)\n\n/_ 为简洁起见省略 _/\n\n![SFC descriptor after parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-after.png)\n\n看起来解析成功了．做得好！\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler2)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/092-compile-sfc-template.md",
    "content": "# 编译模板块\n\n## 切换编译器\n\n`descriptor.script.content` 和 `descriptor.template.content` 包含每个部分的源代码．\\\n让我们成功编译它们．让我们从模板部分开始．\\\n我们已经有了模板编译器．\\\n但是，正如你从以下代码中看到的，\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n```\n\n这假设它将与 Function 构造函数一起使用，所以它在开头包含 `return` 语句．\\\n在 SFC 编译器中，我们只想生成渲染函数，所以让我们使其能够通过编译器选项进行分支．\\\n让我们使其能够接收选项作为编译器的第二个参数，并指定一个名为 `isBrowser` 的标志．\\\n当这个变量为 `true` 时，它输出假设将在运行时 `new` 的代码，当它为 `false` 时，它只是生成代码．\n\n```sh\npwd # ~\ntouch packages/compiler-core/options.ts\n```\n\n`packages/compiler-core/options.ts`\n\n```ts\nexport type CompilerOptions = {\n  isBrowser?: boolean\n}\n```\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(template, defaultOption)\n}\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult, option)\n  return code\n}\n```\n\n`~/packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n```\n\n我还添加了导入语句．我将其更改为将生成的源代码添加到 `output` 数组中．\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\\n\")\n\n      const { descriptor } = parse(code, { filename: id })\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { render }`)\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\n## 问题\n\n现在你应该能够编译渲染函数了．让我们在浏览器的源代码中检查它．\n\n但是，有一个小问题．\n\n当将数据绑定到模板时，我认为你正在使用 `with` 语句．\\\n但是，由于 Vite 处理 ESM 的性质，它无法处理只在非严格模式（sloppy mode）下工作的代码，并且无法处理 `with` 语句．\\\n到目前为止，这还不是问题，因为我只是将包含 `with` 语句的代码（字符串）传递给 Function 构造函数并在浏览器中使其成为函数，但现在它会抛出错误．\\\n你应该看到这样的错误：\n\n> Strict mode code may not include a with statement\n\n这也在 Vite 官方文档中作为故障排除提示进行了描述．\n\n[Syntax Error / Type Error Occurs (Vite)](https://vite.dev/guide/troubleshooting.html#syntax-error-type-error-occurs)\n\n作为临时解决方案，让我们尝试在非浏览器模式下生成不包含 `with` 语句的代码．\n\n具体来说，对于要绑定的数据，让我们尝试通过添加前缀 `_ctx.` 而不是使用 `with` 语句来控制它．\\\n由于这是一个临时解决方案，它不是很严格，但我认为它通常会工作．\\\n（正确的解决方案将在后面的章节中实现．）\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  // 当 `isBrowser` 为 false 时生成不包含 `with` 语句的代码\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n    ${option.isBrowser ? 'with (_ctx) {' : ''}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? '}' : ''}\n}`\n}\n\n// .\n// .\n// .\n\nconst genNode = (\n  node: TemplateChildNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option)\n    case NodeTypes.TEXT:\n      return genText(node)\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (\n  el: ElementNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop, option))\n    .join(', ')}}, [${el.children.map(it => genNode(it, option)).join(', ')}])`\n}\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${\n            option.isBrowser ? '' : '_ctx.' // -------------------- 这里\n          }${prop.exp}`\n        default:\n          // TODO: 其他指令\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n\n// .\n// .\n// .\n\nconst genInterpolation = (\n  node: InterpolationNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? '' : '_ctx.'}${node.content}` // ------------ 这里\n}\n```\n\n![Compiled SFC template render result](/figures/10-minimum-example/compile-sfc-template/compiled-render-result.png)\n\n看起来编译成功了．剩下的就是以同样的方式提取脚本并将其放入默认导出中．\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler3)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/093-compile-sfc-script.md",
    "content": "# 编译脚本块\n\n## 我们想要做什么\n\n现在，SFC 的原始脚本部分看起来像这样：\n\n```ts\nexport default {\n  setup() {},\n}\n```\n\n我想只提取以下部分：\n\n```ts\n  {\n  setup() {},\n}\n```\n\n有什么方法可以做到这一点吗？\n\n如果我可以提取这部分，我可以将其与之前生成的渲染函数很好地混合，并按如下方式导出：\n\n```ts\nconst _sfc_main = {\n  setup() {},\n}\n\nexport default { ..._sfc_main, render }\n```\n\n## 使用外部库\n\n为了实现上述目标，我将使用以下两个库：\n\n- @babel/parser\n- magic-string\n\n### Babel\n\nhttps://babeljs.io\n\n[What is Babel](https://babeljs.io/docs)\n\n如果你熟悉 JavaScript，你可能听说过 Babel．\\\nBabel 是一个用于将 JavaScript 转换为向后兼容版本的工具链．\\\n简单来说，它是一个从 JS 到 JS 的编译器（转译器）．\n\n在这种情况下，我将使用 Babel 不仅作为编译器，还作为解析器．\\\nBabel 有一个内部解析器用于转换为 AST，因为它扮演编译器的角色．\n\nAST 代表抽象语法树，它是 JavaScript 代码的表示．\\\n你可以在这里找到 AST 规范 (https://github.com/estree/estree)。\\\n虽然你可以参考 GitHub md 文件，但我将简要解释 JavaScript 中的 AST．\\\n整个程序由一个 Program AST 节点表示，它包含一个语句数组（为了清晰起见，使用 TS 接口表示）．\n\n```ts\ninterface Program {\n  body: Statement[]\n}\n```\n\nStatement 表示 JavaScript 中的\"语句\"，它是语句的集合．\\\n示例包括\"变量声明语句\"，\"if 语句\"，\"for 语句\"和\"块语句\"．\n\n```ts\ninterface Statement {}\n\ninterface VariableDeclaration extends Statement {\n  /* 省略 */\n}\n\ninterface IfStatement extends Statement {\n  /* 省略 */\n}\n\ninterface ForStatement extends Statement {\n  /* 省略 */\n}\n\ninterface BlockStatement extends Statement {\n  body: Statement[]\n}\n// 还有更多\n```\n\n语句通常在大多数情况下都有一个\"表达式\"．\\\n表达式是可以分配给变量的东西．\\\n示例包括\"对象\"，\"二元运算\"和\"函数调用\"．\n\n```ts\ninterface Expression {}\n\ninterface BinaryExpression extends Expression {\n  operator: '+' | '-' | '*' | '/' // 还有更多，但省略了\n  left: Expression\n  right: Expression\n}\n\ninterface ObjectExpression extends Expression {\n  properties: Property[] // 省略\n}\n\ninterface CallExpression extends Expression {\n  callee: Expression\n  arguments: Expression[]\n}\n\n// 还有更多\n```\n\n如果我们考虑一个 if 语句，它具有以下结构：\n\n```ts\ninterface IfStatement extends Statement {\n  test: Expression // 条件\n  consequent: Statement // 如果条件为真要执行的语句\n  alternate: Statement | null // 如果条件为假要执行的语句\n}\n```\n\n通过这种方式，JavaScript 语法被解析为上述 AST．\\\n我认为对于那些已经为 chibivue 实现了模板编译器的人来说，这个解释很容易理解．（这是同样的事情）\n\n我使用 Babel 的原因有两个．\\\n首先，这只是因为它很麻烦．\\\n如果你之前实现过解析器，在参考 estree 的同时实现 JS 解析器在技术上可能是可能的．\\\n但是，这非常麻烦，对于\"加深对 Vue 的理解\"这一目的来说并不是很重要．\\\n另一个原因是官方 Vue 也在这部分使用 Babel．\n\n### magic-string\n\nhttps://github.com/rich-harris/magic-string\n\n还有另一个我想使用的库．\\\n这个库也被官方 Vue 使用．\\\n它是一个使字符串操作更容易的库．\n\n```ts\nconst input = 'Hello'\nconst s = new MagicString(input)\n```\n\n你可以像这样生成一个实例，并使用实例提供的便利方法来操作字符串．\\\n以下是一些示例：\n\n```ts\ns.append('!!!') // 追加到末尾\ns.prepend('message: ') // 前置到开头\ns.overwrite(9, 13, 'こんにちは') // 在范围内覆写\n```\n\n没有必要强制使用它，但我将使用它来与官方 Vue 保持一致．\n\n无论是 Babel 还是 magic-string，你现在都不需要理解实际用法．\\\n我稍后会解释并对齐实现，所以现在有一个粗略的理解就可以了．\n\n## 重写脚本的默认导出\n\n回顾当前目标：\n\n```ts\nexport default {\n  setup() {},\n  // 其他选项\n}\n```\n\n我想将上面的代码重写为：\n\n```ts\nconst _sfc_main = {\n  setup() {},\n  // 其他选项\n}\n\nexport default { ..._sfc_main, render }\n```\n\n换句话说，如果我可以从原始代码的导出语句中提取导出目标并将其分配给名为 `_sfc_main` 的变量，我将实现目标．\n\n首先，让我们安装必要的库．\n\n```sh\npwd # ~\npnpm add @babel/parser magic-string\n```\n\n创建一个名为 \"rewriteDefault.ts\" 的文件．\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/rewriteDefault.ts\n```\n\n确保函数 \"rewriteDefault\" 可以接收目标源代码作为 \"input\" 和要绑定的变量名作为 \"as\"．\\\n将转换后的源代码作为返回值返回．\n\n`~/packages/compiler-sfc/rewriteDefault.ts`\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // TODO:\n  return ''\n}\n```\n\n首先，让我们处理导出声明不存在的情况．\\\n由于没有导出，绑定一个空对象并完成．\n\n```ts\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`\n  }\n\n  // TODO:\n  return ''\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input)\n}\n```\n\n这里出现了 Babel 解析器和 magic-string．\n\n```ts\nimport { parse } from '@babel/parser'\nimport MagicString from 'magic-string'\n// .\n// .\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  const s = new MagicString(input)\n  const ast = parse(input, {\n    sourceType: 'module',\n  }).program.body\n  // .\n  // .\n}\n```\n\n从这里开始，我们将基于 Babel 解析器获得的 JavaScript AST（抽象语法树）来操作字符串 `s`．\\\n虽然有点长，但我将在源代码的注释中提供额外的解释．\\\n基本上，我们遍历 AST 并基于 `type` 属性编写条件语句，并使用 `magic-string` 的方法操作字符串 `s`．\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  ast.forEach(node => {\n    // 在默认导出的情况下\n    if (node.type === 'ExportDefaultDeclaration') {\n      if (node.declaration.type === 'ClassDeclaration') {\n        // 如果是 `export default class Hoge {}`，将其替换为 `class Hoge {}`\n        s.overwrite(node.start!, node.declaration.id.start!, `class `)\n        // 然后，在末尾添加像 `const ${as} = Hoge;` 这样的代码。\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`)\n      } else {\n        // 对于其他默认导出，将声明部分替换为变量声明。\n        // 例如 1) `export default { setup() {}, }`  ->  `const ${as} = { setup() {}, }`\n        // 例如 2) `export default Hoge`  ->  `const ${as} = Hoge`\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)\n      }\n    }\n\n    // 即使在命名导出的情况下，声明中也可能有默认导出。\n    // 主要有 3 种模式\n    //   1. 在像 `export { default } from \"source\";` 这样的声明情况下\n    //   2. 在像 `export { hoge as default }` from 'source' 这样的声明情况下\n    //   3. 在像 `export { hoge as default }` 这样的声明情况下\n    if (node.type === 'ExportNamedDeclaration') {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === 'ExportSpecifier' &&\n          specifier.exported.type === 'Identifier' &&\n          specifier.exported.name === 'default'\n        ) {\n          // 如果有关键字 `from`\n          if (node.source) {\n            if (specifier.local.name === 'default') {\n              // 1. 在像 `export { default } from \"source\";` 这样的声明情况下\n              // 在这种情况下，将其提取到导入语句中并给它一个名称，然后将其绑定到最终变量。\n              // 例如) `export { default } from \"source\";`  ->  `import { default as __VUE_DEFAULT__ } from 'source'; const ${as} = __VUE_DEFAULT__`\n              const end = specifierEnd(input, specifier.local.end!, node.end!)\n              s.prepend(\n                `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`,\n              )\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`)\n              continue\n            } else {\n              // 2. 在像 `export { hoge as default }` from 'source' 这样的声明情况下\n              // 在这种情况下，将所有说明符按原样重写为导入语句，并将作为默认值的变量绑定到最终变量。\n              // 例如) `export { hoge as default } from \"source\";`  ->  `import { hoge } from 'source'; const ${as} = hoge\n              const end = specifierEnd(\n                input,\n                specifier.exported.end!,\n                node.end!,\n              )\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              )\n\n              // 3. 在像 `export { hoge as default }` 这样的声明情况下\n              // 在这种情况下，简单地将其绑定到最终变量。\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = ${specifier.local.name}`)\n              continue\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!)\n          s.overwrite(specifier.start!, end, ``)\n          s.append(`\\nconst ${as} = ${specifier.local.name}`)\n        }\n      }\n    }\n  })\n  return s.toString()\n}\n\n// 计算声明语句的结束\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false\n  let oldEnd = end\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++\n    } else if (input.charAt(end) === ',') {\n      end++\n      hasCommas = true\n      break\n    } else if (input.charAt(end) === '}') {\n      break\n    }\n  }\n  return hasCommas ? end : oldEnd\n}\n```\n\n现在你可以重写默认导出了．\\\n让我们尝试在插件中使用它．\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse, rewriteDefault } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n\n      const { descriptor } = parse(code, { filename: id })\n\n      // --------------------------- 从这里\n      const SFC_MAIN = '_sfc_main'\n      const scriptCode = rewriteDefault(\n        descriptor.script?.content ?? '',\n        SFC_MAIN,\n      )\n      outputs.push(scriptCode)\n      // --------------------------- 到这里\n\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { ...${SFC_MAIN}, render }`) // 这里\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\n在此之前，让我们做一个小修改．\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n  // 将组件的渲染选项添加到实例\n  const { render } = component\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n}\n```\n\n现在你应该能够渲染了！！！\n\n![Rendered SFC script result](/figures/10-minimum-example/compile-sfc-script/render-sfc-result.png)\n\n样式没有应用，因为不支持，但现在你可以渲染组件了．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/094-compile-sfc-style.md",
    "content": "# 编译样式块\n\n## 虚拟模块\n\n让我们也支持样式．\\\n在 Vite 中，你可以通过使用 `.css` 扩展名来导入 CSS 文件．\n\n```js\nimport 'app.css'\n```\n\n我们将通过使用 Vite 的虚拟模块来实现这一点．\\\n虚拟模块允许你将不存在的文件保存在内存中，就像它们存在一样．\\\n你可以使用 `load` 和 `resolveId` 选项来实现虚拟模块．\n\n```ts\nexport default function myPlugin() {\n  const virtualModuleId = 'virtual:my-module'\n\n  return {\n    name: 'my-plugin', // 必需，在警告和错误中显示\n    resolveId(id) {\n      if (id === virtualModuleId) {\n        return virtualModuleId\n      }\n    },\n    load(id) {\n      if (id === virtualModuleId) {\n        return `export const msg = \"from virtual module\"`\n      }\n    },\n  }\n}\n```\n\n使用这种机制，我们将把 SFC 的样式块作为虚拟 CSS 文件加载．\\\n如前所述，在 Vite 中，导入带有 `.css` 扩展名的文件就足够了，所以我们将考虑创建一个名为 `${SFC 文件名}.css` 的虚拟模块．\n\n## 实现包含 SFC 样式块内容的虚拟模块\n\n对于这个示例，让我们考虑一个名为 \"App.vue\" 的文件，并为其样式部分实现一个名为 \"App.vue.css\" 的虚拟模块．\\\n过程很简单：当加载名为 `**.vue.css` 的文件时，我们将使用 `fs.readFileSync` 从不带 `.css` 的文件路径（即原始 Vue 文件）检索 SFC，解析它以提取样式标签的内容，并将该内容作为代码返回．\n\n```ts\nexport default function vitePluginChibivue(): Plugin {\n  //  ,\n  //  ,\n  //  ,\n  return {\n    //  ,\n    //  ,\n    //  ,\n    resolveId(id) {\n      // 这个 ID 是一个不存在的路径，但我们在 load 中虚拟处理它，所以我们返回 ID 以表明它可以被加载\n      if (id.match(/\\.vue\\.css$/)) return id\n\n      // 对于这里没有返回的 ID，如果文件实际存在，文件将被解析，如果不存在，将抛出错误\n    },\n    load(id) {\n      // 处理加载 .vue.css 时（当声明 import 并加载时）\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, '')\n        const content = fs.readFileSync(filename, 'utf-8') // 正常检索 SFC 文件\n        const { descriptor } = parse(content, { filename }) // 解析 SFC\n\n        // 连接内容并将其作为结果返回\n        const styles = descriptor.styles.map(it => it.content).join('\\n')\n        return { code: styles }\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n      outputs.push(`import '${id}.css'`) // 为 ${id}.css 声明导入语句\n      //  ,\n      //  ,\n      //  ,\n    },\n  }\n}\n```\n\n现在，让我们在浏览器中检查．\n\n![Virtual CSS module request in the browser](/figures/10-minimum-example/compile-sfc-style/load-virtual-css-module.png)\n\n看起来样式被正确应用了．\n\n在浏览器中，你可以看到 CSS 被导入，并且虚拟生成了一个 `.vue.css` 文件．\n\n![Loaded CSS module in the browser](/figures/10-minimum-example/compile-sfc-style/loaded-css-in-browser.png)\n![Generated Vue CSS module](/figures/10-minimum-example/compile-sfc-style/generated-vue-css-module.png)\n\n现在你可以使用 SFC 了！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler4)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/10-minimum-example/100-break.md",
    "content": "# 休息一下\n\n## 最小示例部分结束了！\n\n在开始时，我提到这本书分为几个部分，第一部分\"最小示例部分\"现在已经完成．做得好！\\\n如果你对虚拟 DOM 或补丁渲染感兴趣，你可以继续到基础虚拟 DOM 部分．\\\n如果你想进一步扩展组件，有基础组件部分．如果你对模板中更丰富的表达式（如指令）感兴趣，你可以探索基础模板编译器部分．\\\n如果你对 script setup 或编译器宏感兴趣，你可以继续到基础 SFC 编译器部分．（当然，如果你愿意，你可以全部做！！）\\\n最重要的是，\"最小示例部分\"也是一个值得尊敬的部分，所以如果你觉得，\"我不需要了解得太深入，但我想得到一个大致的想法\"，那么你到这里就足够了．\n\n## 到目前为止我们取得了什么成就？\n\n最后，让我们反思一下我们在最小示例部分做了什么以及取得了什么成就．\n\n## 我们现在知道我们在看什么以及它属于哪里\n\n首先，通过名为 createApp 的初始开发者接口，我们了解了（web 应用）开发者和 Vue 世界是如何连接的．\\\n具体来说，从我们在开始时进行的重构开始，你现在应该了解 Vue 目录结构的基础，它的依赖关系以及开发者正在工作的地方．\\\n让我们比较当前目录和 vuejs/core 的目录．\n\nchibivue\n![Minimum example implementation artifacts](/figures/10-minimum-example/break/minimum-example-artifacts.png)\n\n\\*原始代码太大，无法在截图中显示，所以省略了．\n\nhttps://github.com/vuejs/core\n\n尽管它很小，你现在应该能够在某种程度上阅读和理解每个文件的角色和内容．\\\n我希望你也会挑战自己阅读我们这次没有涵盖的部分的源代码．（你应该能够一点一点地阅读它！）\n\n## 我们现在知道声明式 UI 是如何实现的\n\n通过 h 函数的实现，我们了解了声明式 UI 是如何实现的．\n\n```ts\n// 在内部，它生成一个像 {tag, props, children} 这样的对象，并基于它执行 DOM 操作\nh('div', { id: 'my-app' }, [\n  h('p', {}, ['Hello!']),\n  h(\n    'button',\n    {\n      onClick: () => {\n        alert('hello')\n      },\n    },\n    ['Click me!'],\n  ),\n])\n```\n\n这是虚拟 DOM 之类的东西首次出现的地方．\n\n## 我们现在知道响应式系统是什么以及如何动态更新屏幕\n\n我们了解了 Vue 独特功能响应式系统的实现，它是如何工作的以及它实际上是什么．\n\n```ts\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nfunction reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, {\n    get(target: object, key: string | symbol, receiver: object) {\n      track(target, key)\n      return Reflect.get(target, key, receiver)\n    },\n\n    set(\n      target: object,\n      key: string | symbol,\n      value: unknown,\n      receiver: object,\n    ) {\n      Reflect.set(target, key, value, receiver)\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n```ts\nconst component = {\n  setup() {\n    const state = reactive({ count: 0 }) // 创建代理\n\n    const increment = () => {\n      state.count++ // 触发\n    }\n\n    ;() => {\n      return h('p', {}, `${state.count}`) // 跟踪\n    }\n  },\n}\n```\n\n## 我们现在知道虚拟 DOM 是什么，为什么它有益，以及如何实现它\n\n作为使用 h 函数渲染的改进，我们通过比较了解了使用虚拟 DOM 的高效渲染方法．\n\n```ts\n// 虚拟 DOM 的接口\nexport interface VNode<HostNode = any> {\n  type: string | typeof Text | object\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined\n}\n\n// 首先，调用渲染函数\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  // 第一次，n1 是 null。在这种情况下，每个过程运行 mount\n  patch(null, vnode, container)\n}\n\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\n// 从第二次开始，将前一个 VNode 和当前 VNode 传递给 patch 函数以更新差异\nconst nextVNode = component.render()\npatch(prevVNode, nextVNode)\n```\n\n我了解了组件的结构以及组件之间的交互是如何实现的．\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component\n\n  vnode: VNode\n  subTree: VNode\n  next: VNode | null\n  effect: ReactiveEffect\n  render: InternalRenderFunction\n  update: () => void\n\n  propsOptions: Props\n  props: Data\n  emit: (event: string, ...args: any[]) => void\n\n  isMounted: boolean\n}\n```\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n我了解了编译器是什么以及模板功能是如何实现的．\n\n通过了解编译器是什么并实现模板编译器，我获得了如何实现更原始的类似 HTML 的实现以及如何实现 Vue 特定功能（如 Mustache 语法）的理解．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n我了解了如何通过 Vite 插件实现 SFC 编译器．\n\n通过实现模板编译器并通过 Vite 插件利用它，我获得了如何实现将脚本，模板和样式组合到一个文件中的原始文件格式的理解．\\\n我还了解了 Vite 插件可以做什么，以及 transform 和虚拟模块．\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n## 关于未来\n\n从现在开始，为了使其更实用，我们将在每个部分中更详细地介绍．\\\n我将稍微解释一下每个部分要做什么以及如何进行（政策）．\n\n### 要做什么\n\n从这里开始，它将分为 5 个部分 + 1 个附录．\n\n- 基础虚拟 DOM 部分\n  - 调度器的实现\n  - 不支持的补丁的实现（主要与属性相关）\n  - Fragment 的支持\n- 基础响应式系统部分\n  - ref API\n  - computed API\n  - watch API\n- 基础组件系统部分\n  - provide/inject\n  - 生命周期钩子\n- 基础模板编译器部分\n  - v-on\n  - v-bind\n  - v-for\n  - v-model\n- 基础 SFC 编译器部分\n  - SFC 的基础\n  - script setup\n  - 编译器宏\n- Web 应用程序要点部分（附录）\n\n这部分是附录．\\\n在这部分中，我们将实现在 web 开发中经常与 Vue 一起使用的库．\n\n- store\n- route\n\n我们将涵盖上述两个，但请随意实现其他想到的东西！\n\n### 政策\n\n在最小示例部分，我们相当详细地解释了实现步骤．\\\n到现在，如果你已经实现了它，你应该能够阅读原始 Vue 的源代码．\\\n因此，从现在开始，解释将保持粗略的政策，你将在阅读原始代码或自己思考的同时实现实际代码．\\\n（不-不，这不是我变得懒惰而不愿意详细写作或类似的事情！）\\\n嗯，按照书中所说的实现是有趣的，但一旦它开始成形，自己做更有趣，并且会导致更深的理解．\\\n从这里开始，请将这本书视为一种指导方针，主要内容在原始 Vue 源代码中！\n"
  },
  {
    "path": "book/online-book/src/zh-cn/20-basic-virtual-dom/010-patch-keyed-children.md",
    "content": "# key 属性和补丁渲染（基础虚拟DOM章节开始）\n\n## 关键错误\n\n实际上，当前 chibivue 的补丁渲染中存在一个关键错误．  \n在实现补丁渲染时，\n\n> 关于 patchChildren，需要通过添加 key 等属性来处理动态大小的子元素。\n\n你还记得说过这句话吗？\n\n让我们看看实际会发生什么样的问题．\n在当前的实现中，patchChildren 是这样实现的：\n\n```ts\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\n这是基于 c2（即下一个 vnode）的长度进行循环的．\n换句话说，它基本上只在 c1 和 c2 相同时才能正常工作．\n\n![Index-based patch with equal lengths](/figures/20-basic-virtual-dom/patch-keyed-children/same-length-index-patch.svg)\n\n例如，让我们考虑元素被删除的情况．\n由于补丁循环基于 c2，第四个元素的补丁将不会被执行．\n\n![Deleted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/deleted-child-bug.svg)\n\n当变成这样时，第一到第三个元素只是简单地更新，而第四个元素仍然是来自 c1 的未被删除的元素．\n\n让我们看看实际效果．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ list: ['a', 'b', 'c', 'd'] })\n    const updateList = () => {\n      state.list = ['e', 'f', 'g']\n    }\n\n    return () =>\n      h('div', { id: 'app' }, [\n        h(\n          'ul',\n          {},\n          state.list.map(item => h('li', {}, [item])),\n        ),\n        h('button', { onClick: updateList }, ['update']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n当你点击更新按钮时，应该是这样的：\n\n![Stale child bug rendered in the browser](/figures/20-basic-virtual-dom/patch-keyed-children/stale-child-bug-result.png)\n\n虽然列表应该已经更新为 `[\"e\", \"f\", \"g\"]`，但 \"d\" 仍然存在．\n\n实际上，问题不仅仅是这个．让我们考虑元素被插入的情况．\n目前，由于循环基于 c2，它变成这样：\n\n![Inserted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-index-bug.svg)\n\n然而，实际上，\"新元素\"被插入了，比较应该在 c1 和 c2 的每个 li 1，li 2，li 3 和 li 4 之间进行．\n\n![Inserted child handled with keyed matching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg)\n\n这两个问题的共同点是\"无法确定 c1 和 c2 中需要被视为相同的节点\"．  \n为了解决这个问题，需要为元素分配一个 key，并基于该 key 进行补丁．  \n现在，让我们看看 Vue 文档中对 key 属性的解释．\n\n> 特殊属性 key 主要用作 Vue 虚拟DOM算法的提示，在比较新旧节点列表时识别 VNode。\n\nhttps://v3-migration.vuejs.org/breaking-changes/key-attribute.html\n\n正如预期的那样，对吧？你可能听过\"不要使用索引作为 v-for 的 key\"的建议，但在这一点上，key 被隐式设置为索引，这就是为什么会出现上述问题．（循环基于 c2 的长度，并基于该索引进行补丁）\n\n## 基于 key 属性的补丁\n\n实现这些功能的函数是 `patchKeyedChildren`．（让我们在原始 Vue 中搜索它．）\n\n方法是首先为新节点生成 key 和索引的映射．\n\n```ts\nlet i = 0\nconst l2 = c2.length\nconst e1 = c1.length - 1 // prev node 的结束索引\nconst e2 = l2 - 1 // next node 的结束索引\n\nconst s1 = i // prev node 的开始索引\nconst s2 = i // next node 的开始索引\n\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = s2; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n```\n\n在原始 Vue 中，这个 `patchKeyedChildren` 分为五个部分：\n\n1. sync from start\n2. sync from end\n3. common sequence + mount\n4. common sequence + unmount\n5. unknown sequence\n\n然而，最后一部分 `unknown sequence` 是唯一在功能上必需的，所以我们将从阅读和实现该部分开始．\n\n首先，忘记移动元素，基于 key 对 VNode 进行补丁．\n使用我们之前创建的 `keyToNewIndexMap`，计算 n1 和 n2 的配对并对它们进行补丁．\n此时，如果有新元素需要挂载或需要卸载，也要执行这些操作．\n\n大致来说，它看起来像这样 ↓（我跳过了很多细节．请阅读 vuejs/core 的 renderer.ts 了解更多详细信息．）\n\n```ts\nconst toBePatched = e2 + 1\nconst newIndexToOldIndexMap = new Array(toBePatched) // 新索引到旧索引的映射\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// 基于 e1（旧长度）的循环\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // 如果在新的中不存在，卸载它\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // 形成映射\n    patch(prevChild, c2[newIndex] as VNode, container) // 补丁\n  }\n}\n\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  if (newIndexToOldIndexMap[i] === 0) {\n    // 如果映射不存在（保持初始值），意味着它需要新挂载。（实际上，它存在但不在旧的中）\n    patch(null, nextChild, container, anchor)\n  }\n}\n```\n\n## 移动元素\n\n### 方法\n\n#### Node.insertBefore\n\n目前，我们只基于 key 匹配更新每个元素，所以如果元素被移动，我们需要编写代码将其移动到所需位置．\n\n首先，让我们谈谈如何移动元素．我们在 `nodeOps` 的 `insert` 函数中指定锚点．锚点，顾名思义，是一个锚点，如果你查看在 runtime-dom 中实现的 `insert` 方法，你可以看到它是用 `insertBefore` 方法实现的．\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // .\n  // .\n  // .\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\n通过将节点作为第二个参数传递给此方法，节点将被插入到该节点之前．  \nhttps://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore\n\n我们使用这个方法来实际移动 DOM．\n\n#### LIS（最长递增子序列）\n\n现在，让我们谈谈如何编写移动算法．这部分稍微复杂一些．  \n与运行 JavaScript 相比，DOM 操作的成本要高得多，所以我们希望尽可能减少不必要的移动次数．  \n这就是我们使用\"最长递增子序列\"（LIS）算法的地方．  \n这个算法在数组中找到最长的递增子序列．  \n递增子序列是元素按递增顺序排列的子序列．  \n例如，给定以下数组：\n\n```\n[2, 4, 1, 7, 5, 6]\n```\n\n有几个递增子序列：\n\n```\n[2, 4]\n[2, 5]\n.\n.\n[2, 4, 7]\n[2, 4, 5]\n.\n.\n[2, 4, 5, 6]\n.\n.\n[1, 7]\n.\n.\n[1, 5, 6]\n```\n\n这些是元素递增的子序列．最长的一个是\"最长递增子序列\"．  \n在这种情况下，`[2, 4, 5, 6]` 是最长递增子序列．在 Vue 中，对应于 2，4，5 和 6 的索引被视为结果数组（即 `[0, 1, 4, 5]`）．\n\n顺便说一下，这里是一个示例函数：\n\n```ts\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice()\n  const result = [0]\n  let i, j, u, v, c\n  const len = arr.length\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i]\n    if (arrI !== 0) {\n      j = result[result.length - 1]\n      if (arr[j] < arrI) {\n        p[i] = j\n        result.push(i)\n        continue\n      }\n      u = 0\n      v = result.length - 1\n      while (u < v) {\n        c = (u + v) >> 1\n        if (arr[result[c]] < arrI) {\n          u = c + 1\n        } else {\n          v = c\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1]\n        }\n        result[u] = i\n      }\n    }\n  }\n  u = result.length\n  v = result[u - 1]\n  while (u-- > 0) {\n    result[u] = v\n    v = p[v]\n  }\n  return result\n}\n```\n\n我们将使用这个函数从 `newIndexToOldIndexMap` 计算最长递增子序列，并基于此，我们将使用 `insertBefore` 插入其他节点．\n\n### 具体示例\n\n这里有一个具体的示例来让它更容易理解．\n\n让我们考虑两个 VNode 数组，`c1` 和 `c2`．`c1` 表示更新前的状态，`c2` 表示更新后的状态．每个 VNode 都有一个 `key` 属性（实际上，它包含更多信息）．\n\n```js\nc1 = [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }]\nc2 = [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }]\n```\n\n首先，让我们基于 `c2` 生成 `keyToNewIndexMap`（key 到 `c2` 中索引的映射）．\n※ 这是之前介绍的代码．\n\n```ts\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = 0; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n\n// keyToNewIndexMap = { a: 0, b: 1, d: 2, c: 3 }\n```\n\n接下来，让我们生成 `newIndexToOldIndexMap`．\n※ 这是之前介绍的代码．\n\n```ts\n// 初始化\n\nconst toBePatched = c2.length\nconst newIndexToOldIndexMap = new Array(toBePatched) // 新索引到旧索引的映射\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// newIndexToOldIndexMap = [0, 0, 0, 0]\n```\n\n```ts\n// 执行补丁并生成用于移动的 newIndexToOldIndexMap\n\n// 基于 e1（旧长度）的循环\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // 如果在新数组中不存在，卸载它\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // 形成映射\n    patch(prevChild, c2[newIndex] as VNode, container) // 执行补丁\n  }\n}\n\n// newIndexToOldIndexMap = [1, 2, 4, 3]\n```\n\n然后，从获得的 `newIndexToOldIndexMap` 中获取最长递增子序列（新实现从这里开始）．\n\n```ts\nconst increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)\n// increasingNewIndexSequence  = [0, 1, 3]\n```\n\n```ts\nj = increasingNewIndexSequence.length - 1\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  const anchor =\n    nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor // ※ parentAnchor 暂时可以认为是参数中接收到的 anchor。\n\n  if (newIndexToOldIndexMap[i] === 0) {\n    // newIndexToOldIndexMap 的初始值是 0，所以如果是 0，则判断为不存在到旧元素的映射，即是新元素。\n    patch(null, nextChild, container, anchor)\n  } else {\n    // 如果 i 和 increasingNewIndexSequence[j] 不匹配，则进行移动\n    if (j < 0 || i !== increasingNewIndexSequence[j]) {\n      move(nextChild, container, anchor)\n    } else {\n      j--\n    }\n  }\n}\n```\n\n### 让我们实现它．\n\n现在我们已经详细解释了方法，让我们实际实现 `patchKeyedChildren`．以下是步骤总结：\n\n1. 准备用于桶接力的 `anchor`（用于在 `move` 中插入）．\n2. 基于 `c2` 创建 key 和索引的映射．\n3. 基于 key 映射创建 `c2` 和 `c1` 中索引的映射．\n   在这个阶段，在基于 `c1` 和基于 `c2` 的循环中执行补丁过程（不包括 `move`）．\n4. 基于步骤 3 中获得的映射找到最长递增子序列．\n5. 基于步骤 4 中获得的子序列和 `c2` 执行 `move`．\n\n你可以参考原始 Vue 实现或 chibivue 实现作为指导．（我建议在跟随的同时阅读原始 Vue 实现．）\n"
  },
  {
    "path": "book/online-book/src/zh-cn/20-basic-virtual-dom/020-bit-flags.md",
    "content": "# 使用位表示 VNode\n\n## 使用位表示 VNode 的类型\n\nVNode 有各种类型．例如，目前实现的包括：\n\n- 组件节点\n- 元素节点\n- 文本节点\n- 子元素是否为文本\n- 子元素是否为数组\n\n在未来，将实现更多类型的 VNode．例如，slot，keep-alive，suspense，teleport 等．\n\n目前，分支是使用 `type === Text`，`typeof type === \"string\"`，`typeof type === \"object\"` 等条件进行的．\n\n逐一检查这些条件是低效的，所以让我们尝试按照原始实现使用位来表示它们．在 Vue 中，这些位被称为\"ShapeFlags\"．顾名思义，它们表示 VNode 的形状．（严格来说，在 Vue 中，ShapeFlags 和 Text，Fragment 等符号用于确定 VNode 的类型．）\nhttps://github.com/vuejs/core/blob/main/packages/shared/src/shapeFlags.ts\n\n位标志是指将数字的每一位视为特定标志．\n\n让我们以下面的 VNode 为例：\n\n```ts\nconst vnode = {\n  type: 'div',\n  children: [\n    { type: 'p', children: ['hello'] },\n    { type: 'p', children: ['hello'] },\n  ],\n}\n```\n\n![ShapeFlags pack VNode shape into bits](/figures/20-basic-virtual-dom/bit-flags/shape-flag-overview.svg)\n\n首先，标志的初始值是 0．（为了简单起见，这个解释使用 8 位．）\n\n```ts\nlet shape = 0b0000_0000\n```\n\n现在，这个 VNode 是一个元素并且有一个子元素数组，所以设置 ELEMENT 标志和 ARRAY_CHILDREN 标志．\n\n```ts\nshape = shape | ShapeFlags.ELEMENT | ELEMENT.ARRAY_CHILDREN // 0x00010001\n```\n\n通过这种方式，我们可以使用一个名为\"shape\"的数字来表示这个 VNode 是一个元素并且有一个子元素数组的信息．我们可以通过在渲染器或代码的其他部分的分支中使用它来高效地管理 VNode 的类型．\n\n```ts\nif (vnode.shape & ShapeFlags.ELEMENT) {\n  // vnode 是元素时的处理\n}\n```\n\n由于这次我们没有实现所有的 ShapeFlags，请尝试实现以下内容作为练习：\n\n```ts\nexport const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n```\n\n你需要做的是：\n\n- 在 shared/shapeFlags.ts 中定义标志\n- 在 runtime-core/vnode.ts 中定义 shape\n  ```ts\n  export interface VNode<HostNode = any> {\n    shapeFlag: number\n  }\n  ```\n  添加这个并在 createVNode 等函数中计算标志．\n- 在渲染器中基于 shape 实现分支逻辑．\n\n这就是本章的解释．让我们开始实现吧！\n"
  },
  {
    "path": "book/online-book/src/zh-cn/20-basic-virtual-dom/030-scheduler.md",
    "content": "# 调度器\n\n## 调度 Effect\n\n首先，看看这段代码：\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      message: 'Hello World',\n    })\n    const updateState = () => {\n      state.message = 'Hello ChibiVue!'\n      state.message = 'Hello ChibiVue!!'\n    }\n\n    return () => {\n      console.log('😎 rendered!')\n\n      return h('div', { id: 'app' }, [\n        h('p', {}, [`message: ${state.message}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n当按钮被点击时，`state.message` 上的 `set` 函数被调用两次，所以自然地，`trigger` 函数也会被执行两次．这意味着虚拟 DOM 将被计算两次，补丁也会被执行两次．\n\n![Effect result before scheduler batching](/figures/20-basic-virtual-dom/scheduler/non-scheduled-effect.png)\n\n然而，实际上，补丁只需要执行一次，在第二次触发时．  \n因此，我们将实现一个调度器．调度器负责管理任务的执行顺序和控制．Vue 调度器的作用之一是在队列中管理响应式 effect，并在可能的情况下合并它们．\n\n## 使用队列管理进行调度\n\n具体来说，我们将有一个队列来管理作业．每个作业都有一个 ID，当新作业入队时，如果队列中已经有相同 ID 的作业，它将被覆盖．\n\n```ts\nexport interface SchedulerJob extends Function {\n  id?: number\n}\n\nconst queue: SchedulerJob[] = []\n\nexport function queueJob(job: SchedulerJob) {\n  if (\n    !queue.length ||\n    !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)\n  ) {\n    if (job.id == null) {\n      queue.push(job)\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job)\n    }\n    queueFlush()\n  }\n}\n```\n\n至于作业 ID，在这种情况下，我们希望按组件分组，所以我们将为每个组件分配一个唯一标识符（UID）并将其用作作业 ID．  \nUID 只是通过递增计数器获得的标识符．\n\n## ReactiveEffect 和调度器\n\n目前，ReactiveEffect 具有以下接口（部分省略）：\n\n```ts\nclass ReactiveEffect {\n  public fn: () => T,\n\n  run() {}\n}\n```\n\n随着调度器的实现，让我们做一个小改变．  \n目前，我们将函数注册到 `fn` 作为 effect，但这次，让我们将其分为\"主动执行的 effect\"和\"被动执行的 effect\"．  \n响应式 effect 可以由设置 effect 的一方主动执行，也可以在被添加到依赖项（`dep`）后被某些外部操作触发而被动执行．  \n对于后一种类型的 effect，它被添加到多个 `depsMap` 并由多个源触发，需要调度（另一方面，如果它被明确主动调用，则不需要这样的调度）．\n\n让我们考虑一个具体的例子．在渲染器的 `setupRenderEffect` 函数中，你可能有以下实现：\n\n```ts\nconst effect = (instance.effect = new ReactiveEffect(() => componentUpdateFn))\nconst update = (instance.update = () => effect.run())\nupdate()\n```\n\n这里创建的 `effect`，它是一个 `reactiveEffect`，稍后在执行 `setup` 函数时将被响应式对象跟踪．这显然需要调度的实现（因为它将从各个地方被触发）．  \n然而，关于这里调用的 `update()` 函数，它应该简单地执行 effect，所以不需要调度．  \n你可能会想，\"那我们不能直接调用 `componentUpdateFn` 吗？\"但请记住 `run` 函数的实现．简单地调用 `componentUpdateFn` 不会设置 `activeEffect`．  \n所以，让我们分离\"主动执行的 effect\"和\"被动执行的 effect（需要调度的 effect）\"．\n\n作为本章的最终接口，它将如下所示：\n\n```ts\n// ReactiveEffect 的第一个参数是主动执行的 effect，第二个参数是被动执行的 effect\nconst effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n  queueJob(update),\n))\nconst update: SchedulerJob = (instance.update = () => effect.run())\nupdate.id = instance.uid\nupdate()\n```\n\n在实现方面，除了 `fn` 之外，`ReactiveEffect` 将有一个 `scheduler` 函数，在 `triggerEffect` 函数中，如果存在调度器，将首先执行调度器．\n\n```ts\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null\n  );\n}\n```\n\n```ts\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler()\n  } else {\n    effect.run() // 如果没有调度器，正常执行 effect\n  }\n}\n```\n\n---\n\n现在，让我们在阅读源代码的同时实现队列管理调度和 effect 分类！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/040_scheduler)\n\n## 我们需要 nextTick\n\n如果你在实现调度器时阅读了源代码，你可能已经注意到\"nextTick\"的出现并想知道它是否在这里使用．首先，让我们谈谈这次我们想要实现的任务．请看这段代码：\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = () => {\n      state.count++\n\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n尝试点击这个按钮并查看控制台．\n\n![Old DOM state before nextTick](/figures/20-basic-virtual-dom/scheduler/old-state-dom.png)\n\n即使我们在更新 `state.count` 后输出到控制台，信息也是过时的．这是因为当状态更新时，DOM 不会立即更新，在控制台输出时，DOM 仍处于旧状态．\n\n这就是\"nextTick\"发挥作用的地方．\n\nhttps://vuejs.org/api/general.html#nexttick\n\n\"nextTick\"是调度器的一个 API，它允许你等待直到调度器应用 DOM 更改．\"nextTick\"的实现非常简单．它只是保持调度器中正在刷新的作业（promise）并将其连接到\"then\"．\n\n```ts\nexport function nextTick<T = void>(\n  this: T,\n  fn?: (this: T) => void,\n): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise\n  return fn ? p.then(this ? fn.bind(this) : fn) : p\n}\n```\n\n当作业完成时（promise 被解析），传递给\"nextTick\"的回调被执行．（如果队列中没有作业，它连接到\"resolvedPromise\"的\"then\"）自然地，\"nextTick\"本身也返回一个 Promise，所以作为开发者接口，你可以传递回调或 await \"nextTick\"．\n\n```ts\nimport { createApp, h, reactive, nextTick } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = async () => {\n      state.count++\n\n      await nextTick() // 等待\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n现在，让我们实际重写当前调度器的实现以保持\"currentFlushPromise\"并实现\"nextTick\"！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/050_next_tick)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/20-basic-virtual-dom/040-patch-other-attrs.md",
    "content": "# 无法处理的 Props 的补丁\n\n在本章中，让我们为目前无法处理的 Props 实现补丁．\n以下是一些需要处理的 Props 示例，但请尝试通过参考原始实现来实现它们，同时自己填补缺失的部分！\n通过这样做，它应该变得更加实用！\n\n没有什么特别新的东西．基于我们到目前为止所做的，应该能够充分实现它．\n\n我想关注的是 runtime-dom/modules 的实现．\n\n## 新旧比较\n\n目前，更新只能基于 n2 的 props 进行．\n让我们基于 n1 和 n2 进行更新．\n\n```ts\nconst oldProps = n1.props || {}\nconst newProps = n2.props || {}\n```\n\n存在于 n1 但不存在于 n2 中的 Props 应该被删除．\n另外，如果即使两者都存在但值相同，也不需要补丁，所以跳过它．\n\n## class / style（注意）\n\n有多种绑定 class 和 style 的方法．\n\n```html\n<p class=\"static property\">hello</p>\n<p :class=\"'dynamic property'\">hello</p>\n<p :class=\"['dynamic', 'property', 'array']\">hello</p>\n<p :class=\"{ dynamic: true, property: true, array: true}\">hello</p>\n<p class=\"static property\" :class=\"'mixed dynamic property'\">hello</p>\n<p style=\"static: true;\" :style=\"{ mixed-dynamic: 'true' }\">hello</p>\n```\n\n要实现这些，需要在基础模板编译器部分解释的 `transform` 概念．\n只要不偏离原始 Vue 的设计，它可以在任何地方实现，但我们在这里跳过它，因为我们想在本书中遵循原始 Vue 的设计．\n\n## innerHTML / textContent\n\ninnerHTML 和 textContent 与其他 Props 相比有点特殊．\\\n这是因为如果具有此 Prop 的元素有子元素，它们需要被卸载．\n\n例如，考虑以下情况：\n\n```ts\nh('div', { innerHTML: '<p>hello</p>' }, [\n  h(SomeComponent, {}, [])\n])\n```\n\n在这种情况下，div 元素的内容将被 `innerHTML` 覆盖为 `<p>hello</p>`．\\\n然而，作为 children 传递的 `SomeComponent` 已经存在于虚拟 DOM 中，如果不正确卸载它，将会发生以下问题：\n\n- 事件监听器不会被移除\n- 组件生命周期钩子（如 onUnmounted）不会被调用\n- 可能导致内存泄漏\n\n因此，在设置 innerHTML 或 textContent 时，需要卸载现有的子元素．\n\n### 实现\n\n首先，扩展 `patchProp` 的类型定义以接受 `prevChildren` 和 `unmountChildren`．\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[], // 添加\n    unmountChildren?: (children: VNode<HostNode>[]) => void, // 添加\n  ): void;\n  // ...\n}\n```\n\n接下来，在 `patchDOMProp` 函数中实现 innerHTML/textContent 的处理．\n\n`~/packages/runtime-dom/modules/props.ts`\n\n```ts\nexport function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === 'innerHTML' || key === 'textContent') {\n    // 如果存在子元素则卸载\n    if (prevChildren) {\n      unmountChildren(prevChildren)\n    }\n    el[key] = value == null ? '' : value\n    return\n  }\n\n  // ... (其他 props 的处理)\n}\n```\n\n然后，在从 `patchProp` 调用 `patchDOMProp` 时传递 `prevChildren` 和 `unmountChildren`．\n\n`~/packages/runtime-dom/patchProp.ts`\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === 'style') {\n    patchStyle(el, prevValue, nextValue)\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue)\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren) // 传递 prevChildren, unmountChildren\n  } else {\n    patchAttr(el, key, nextValue)\n  }\n}\n```\n\n最后，在 renderer.ts 中调用 `hostPatchProp` 时传递适当的参数．\n\n`~/packages/runtime-core/renderer.ts` 的 `mountElement` 和 `patchElement`\n\n```ts\nconst mountElement = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children as VNode[], el, anchor)\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(\n        el,\n        key,\n        null,\n        props[key],\n        vnode.children as VNode[], // 添加\n        unmountChildren, // 添加\n      )\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\n现在，当使用 innerHTML 或 textContent 时，现有的子元素将被正确卸载．\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/060_other_props)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/30-basic-reactivity-system/005-reactivity-optimization.md",
    "content": "# 响应式优化\n\n::: info 关于本章\n本章介绍 Vue 3.6 中将引入的基于 [alien-signals](https://github.com/stackblitz/alien-signals) 的响应式系统优化．\\\nchibivue 的实现也基于此算法进行了更新．\n:::\n\n## 背景\n\nVue.js 的响应式系统在 Vue 3.4 中进行了重大性能优化．然而，Vue 3.5 切换到了类似 Preact 的 pull-based 算法，改变了响应式系统的方向．\n\n为了进一步研究 push-pull based 实现，Vue 的核心贡献者 Johnson Chu 开发了独立项目 [alien-signals](https://github.com/stackblitz/alien-signals)．\n\nalien-signals 是基于 Vue 3.4 响应式系统重新实现的信号库，具有以下特点：\n\n- **轻量**：最小的内存占用\n- **快速**：约为 Vue 3.4 响应式系统的 4 倍（400%）性能\n- **内存高效**：约 13% 的内存使用量减少\n\n这些成果将在 Vue 3.6 中被移植到 Vue 核心的响应式系统中．\n\n参考：[vuejs/core#12349](https://github.com/vuejs/core/pull/12349)\n\n<KawaikoNote variant=\"surprise\" title=\"性能提升 4 倍！\">\n\nalien-signals 基于 Vue 3.4 的响应式系统重新实现，竟然实现了**约 4 倍**的性能提升！\\\n随着这项成果被整合到 Vue 3.6 中，所有 Vue 用户都将受益于这些优化．\n\n</KawaikoNote>\n\n## Push-Pull 响应式算法\n\n让我们简要解释 alien-signals 采用的 Push-Pull 算法．\n\n### Push-based 与 Pull-based\n\n响应式系统主要有两种方法：\n\n**Push-based（推送型）**\n\n当依赖项发生变化时，立即更新所有依赖的 computed 值．\n\n```\nsignal 变化 → 立即更新所有 computed → 执行 effect\n```\n\n优点：始终保证最新值\n缺点：即使未使用的 computed 也会被更新\n\n**Pull-based（拉取型）**\n\ncomputed 值仅在需要时（读取时）才计算．\n\n```\nsignal 变化 → (不做任何事) → 在 effect 中读取 computed → 此时计算\n```\n\n优点：仅执行必要的计算\n缺点：读取时有开销\n\n### Push-Pull（混合型）\n\nalien-signals 和 Vue 3.6 采用的 Push-Pull 算法结合了两者的优点：\n\n1. **Push 阶段**：当 signal 变化时，在依赖的 computed 上设置\"dirty\"标志\n2. **Pull 阶段**：当读取 computed 时，如果是 dirty 则重新计算\n\n```\nsignal 变化 → 传播 dirty 标志 → 在 effect 中读取 computed → 如果 dirty 则重新计算\n```\n\n![Push-Pull reactivity overview](/figures/30-basic-reactivity-system/reactivity-optimization/push-pull-overview.svg)\n\n这种方法提供：\n- 避免不必要的计算（Pull 的优点）\n- 高效的依赖跟踪（Push 的优点）\n\n<KawaikoNote variant=\"funny\" title=\"两全其美！\">\n\nPush-Pull 算法是一种结合了 Push 和 Pull 两者优点的聪明方法．\\\n\"发生变化时只传播 dirty 标志，实际计算等到需要时再做\"的策略，彻底消除了不必要的计算！\n\n</KawaikoNote>\n\n## alien-signals 的基本 API\n\nalien-signals 提供了非常简单的 API：\n\n```ts\nimport { signal, computed, effect } from 'alien-signals'\n\n// signal：创建响应式值\nconst count = signal(1)\n\n// 读取值\nconsole.log(count()) // 1\n\n// 更新值\ncount(2)\n\n// computed：创建派生值\nconst double = computed(() => count() * 2)\nconsole.log(double()) // 4\n\n// effect：注册副作用\neffect(() => {\n  console.log(`Count is: ${count()}`)\n})\n\ncount(3) // 输出 \"Count is: 3\"\n```\n\n与 Vue 的 `ref` 和 `reactive` 比较：\n\n| alien-signals | Vue |\n|--------------|-----|\n| `signal(value)` | `ref(value)` |\n| `signal()` 读取 | `.value` 读取 |\n| `signal(newValue)` 写入 | `.value = newValue` 写入 |\n| `computed(() => ...)` | `computed(() => ...)` |\n| `effect(() => ...)` | `watchEffect(() => ...)` |\n\n## 实现概述\n\n::: warning\n本章不会完全移植 alien-signals 的实现，而是解释其概念和基本机制．\\\n要完全理解，请参阅 [alien-signals 源代码](https://github.com/stackblitz/alien-signals) 或 [Vue 3.6 的 PR](https://github.com/vuejs/core/pull/12349)．\n:::\n\n<KawaikoNote variant=\"base\" title=\"请查看 Johnson 的解说！\">\n\n如果您想了解更多关于 alien-signals 算法的内容，我们推荐阅读作者 Johnson Chu 撰写的解说！\\\n[https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30)\n\n</KawaikoNote>\n\n### 双向链表\n\nalien-signals 的重要优化之一是使用双向链表管理依赖关系．\n\n传统的 Vue 实现使用 Set 管理依赖：\n\n```ts\n// 传统实现\nclass Dep {\n  subscribers = new Set<ReactiveEffect>()\n\n  track() {\n    if (activeEffect) {\n      this.subscribers.add(activeEffect)\n    }\n  }\n\n  trigger() {\n    this.subscribers.forEach(effect => effect.run())\n  }\n}\n```\n\nalien-signals 使用链表：\n\n```ts\n// alien-signals 风格\ninterface Link {\n  dep: Dep\n  sub: Subscriber\n  prevDep: Link | undefined  // 同一 subscriber 的前一个 dep 的引用\n  nextDep: Link | undefined  // 同一 subscriber 的下一个 dep 的引用\n  prevSub: Link | undefined  // 同一 dep 的前一个 subscriber 的引用\n  nextSub: Link | undefined  // 同一 dep 的下一个 subscriber 的引用\n}\n```\n\n这种结构提供：\n- 减少内存使用（避免 Set 的开销）\n- O(1) 的依赖添加/删除\n- 减少 GC 压力\n\n### 版本管理\n\n另一个重要优化是使用版本号进行 dirty 检查：\n\n```ts\nlet globalVersion = 0\n\nfunction triggerRef(ref: Ref) {\n  globalVersion++\n  ref.version = globalVersion\n  // 向 subscribers 传播 dirty\n}\n\nfunction computedGetter(computed: ComputedRef) {\n  if (computed.globalVersion !== globalVersion) {\n    // 依赖项之一可能已更新\n    if (checkDirty(computed)) {\n      // 如果实际是 dirty 则重新计算\n      computed.value = computed.getter()\n    }\n    computed.globalVersion = globalVersion\n  }\n  return computed.value\n}\n```\n\n使用全局版本提供：\n- 高效判断 computed 是否真的需要重新计算\n- 避免不必要的依赖遍历\n\n## chibivue 中的实现\n\nchibivue 基于 alien-signals 算法实现响应式系统．\n\n主要文件：\n- `packages/reactivity/dep.ts` - 依赖管理\n- `packages/reactivity/effect.ts` - effect 实现\n- `packages/reactivity/ref.ts` - ref 实现\n- `packages/reactivity/computed.ts` - computed 实现\n\n基本结构：\n\n```ts\n// packages/reactivity/dep.ts\nexport interface Link {\n  dep: Dep\n  sub: Subscriber\n  version: number\n  prevDep: Link | undefined\n  nextDep: Link | undefined\n  prevSub: Link | undefined\n  nextSub: Link | undefined\n}\n\nexport class Dep {\n  version = 0\n  link: Link | undefined = undefined\n  subs: Link | undefined = undefined\n\n  track(): Link | undefined {\n    // 将 activeEffect 注册为订阅者\n  }\n\n  trigger(): void {\n    // 通知所有订阅者\n  }\n}\n```\n\n```ts\n// packages/reactivity/effect.ts\nexport class ReactiveEffect<T = any> implements Subscriber {\n  deps: Link | undefined = undefined\n  depsTail: Link | undefined = undefined\n\n  run(): T {\n    // 执行 effect 函数并收集依赖\n  }\n}\n```\n\n后续章节将基于这个优化的响应式系统进行构建．\n\n<KawaikoNote variant=\"base\" title=\"继续前进\">\n\n你理解 alien-signals 的概念了吗？\\\n链表和版本管理一开始可能感觉很难，但随着你编写代码，自然会理解的．\\\n让我们在下一章中基于这个优化的机制实现 ref 和 computed！\n\n</KawaikoNote>\n\n## 总结\n\n- Vue 3.6 将引入基于 alien-signals 的优化响应式系统\n- Push-Pull 算法实现高效的 dirty 检查和延迟评估\n- 双向链表的依赖管理提高内存效率\n- 基于版本号的 dirty 检查避免不必要的重新计算\n\n从下一章开始，我们将在这个优化的响应式系统之上实现 ref 和 computed 等 API．\n\n## 参考链接\n\n- [stackblitz/alien-signals](https://github.com/stackblitz/alien-signals) - alien-signals 官方仓库\n- [alien-signals 算法详细解说](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30) - 作者 Johnson Chu 撰写的详细解说\n- [vuejs/core#12349](https://github.com/vuejs/core/pull/12349) - Vue 3.6 移植 PR\n- [掌握 Vue 3.6 的 Alien Signals](https://medium.com/@revanthkumarpatha/mastering-vue-3-6s-alien-signals-practical-examples-and-use-cases-7df02a159d8a) - Medium 文章\n"
  },
  {
    "path": "book/online-book/src/zh-cn/30-basic-reactivity-system/010-ref-api.md",
    "content": "# ref api（基础响应式系统开始）\n\n::: tip 前置知识\n在阅读本章之前，我们建议阅读[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)以了解基于 alien-signals 的优化响应式系统的概念．\n:::\n\n## ref api 的回顾（和实现）\n\nVue.js 有各种与响应式相关的 API，其中 ref 特别著名．  \n即使在官方文档中，它也作为\"响应式核心\"名称下的第一个主题被介绍．  \nhttps://vuejs.org/api/reactivity-core.html#ref\n\n那么，什么是 ref API？\n根据官方文档，\n\n> ref 对象是可变的 - 即你可以为 .value 分配新值。它也是响应式的 - 即对 .value 的任何读取操作都会被跟踪，写入操作会触发相关的 effect。\n\n> 如果将对象分配为 ref 的值，该对象会通过 reactive() 变成深度响应式的。这也意味着如果对象包含嵌套的 ref，它们将被深度解包。\n\n（引用：https://vuejs.org/api/reactivity-core.html#ref）\n\n简而言之，ref 对象有两个特征：\n\n- 对 value 属性的获取/设置操作会触发 track/trigger．\n- 当对象被分配给 value 属性时，value 属性变成响应式对象．\n\n用代码来解释，\n\n```ts\nconst count = ref(0)\ncount.value++ // effect（特征 1）\n\nconst state = ref({ count: 0 })\nstate.value = { count: 1 } // effect（特征 1）\nstate.value.count++ // effect（特征 2）\n```\n\n就是这个意思．\n\n在你能够区分 ref 和 reactive 之前，你可能会混淆 `ref(0)` 和 `reactive({ value: 0 })` 之间的区别，但考虑到上面提到的两个特征，你可以看到它们有完全不同的含义．\nref 不会生成像 `{ value: x }` 这样的响应式对象．对 value 的获取/设置操作的 track/trigger 是由 ref 的实现执行的，如果对应于 x 的部分是对象，它就会变成响应式对象．\n\n在实现方面，它看起来像这样：\n\n```ts\nclass RefImpl<T> {\n  private _value: T\n  public dep?: Dep = undefined\n\n  get value() {\n    trackRefValue(this)\n  }\n\n  set value(newVal) {\n    this._value = toReactive(newVal)\n    triggerRefValue(this)\n  }\n}\n\nconst toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value\n```\n\n让我们在查看源代码的同时实现 ref！\n有各种函数和类，但现在，专注于 RefImpl 类和 ref 函数就足够了．\n\n一旦你能运行以下源代码，就可以了！\n（注意：模板编译器需要单独支持 ref，所以它不会工作）\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['Increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/010_ref)\n\n## shallowRef\n\n现在，让我们继续实现更多与 ref 相关的 API．  \n如前所述，ref 的特征之一是\"当对象被分配给 value 属性时，value 属性变成响应式对象\"．shallowRef 没有这个特征．\n\n> 与 ref() 不同，浅层 ref 的内部值按原样存储和暴露，不会被深度响应式化。只有 .value 访问是响应式的。\n\n（引用：https://vuejs.org/api/reactivity-advanced.html#shallowref）\n\n任务非常简单．我们可以按原样使用 RefImpl 的实现，并跳过 `toReactive` 部分．  \n让我们在阅读源代码的同时实现它！\n\n一旦你能运行以下源代码，就可以了！\n\n```ts\nimport { createApp, h, shallowRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // 点击不会触发重新渲染\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n### triggerRef\n\n如前所述，由于 shallow ref 的值不是响应式对象，对它的更改不会触发 effect．  \n然而，值本身是一个对象，所以它已经被更改了．  \n因此，有一个 API 可以强制触发 effect．它就是 triggerRef．\n\nhttps://vuejs.org/api/reactivity-advanced.html#triggerref\n\n```ts\nimport { createApp, h, shallowRef, triggerRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n    const forceUpdate = () => {\n      triggerRef(state)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // 点击不会触发重新渲染\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n\n        h(\n          'button', // 渲染更新为 state.value.count 当前持有的值\n          { onClick: forceUpdate },\n          ['force update !'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/020_shallow_ref)\n\n## toRef\n\ntoRef 是一个为响应式对象的属性生成 ref 的 API．\n\nhttps://vuejs.org/api/reactivity-utilities.html#toref\n\n它经常用于将 props 的特定属性转换为 ref．\n\n```ts\nconst count = toRef(props, 'count')\nconsole.log(count.value)\n```\n\n由 toRef 创建的 ref 与原始响应式对象同步．\n如果你对这个 ref 进行更改，原始响应式对象也会被更新，如果原始响应式对象有任何更改，这个 ref 也会被更新．\n\n```ts\nimport { createApp, h, reactive, toRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const stateCountRef = toRef(state, 'count')\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`state.count: ${state.count}`]),\n        h('p', {}, [`stateCountRef.value: ${stateCountRef.value}`]),\n        h('button', { onClick: () => state.count++ }, ['updateState']),\n        h('button', { onClick: () => stateCountRef.value++ }, ['updateRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n让我们在阅读源代码的同时实现！\n\n※ 从 v3.3 开始，toRef 添加了规范化功能．chibivue 不实现此功能．  \n请查看官方文档中的签名以获取更多详细信息！（https://vuejs.org/api/reactivity-utilities.html#toref）\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/030_to_ref)\n\n## toRefs\n\n为响应式对象的所有属性生成 ref．\n\nhttps://vuejs.org/api/reactivity-utilities.html#torefs\n\n```ts\nimport { createApp, h, reactive, toRefs } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ foo: 1, bar: 2 })\n    const stateAsRefs = toRefs(state)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`[state]: foo: ${state.foo}, bar: ${state.bar}`]),\n        h('p', {}, [\n          `[stateAsRefs]: foo: ${stateAsRefs.foo.value}, bar: ${stateAsRefs.bar.value}`,\n        ]),\n        h('button', { onClick: () => state.foo++ }, ['update state.foo']),\n        h('button', { onClick: () => stateAsRefs.bar.value++ }, [\n          'update stateAsRefs.bar.value',\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n这可以使用 toRef 的实现轻松实现．\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/040_to_refs)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/30-basic-reactivity-system/020-computed-watch.md",
    "content": "# computed / watch api\n\n::: warning\n这里解释的实现基于当前草拟的[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的内容将更新以与其保持一致．\n:::\n\n## computed 的回顾（和实现）\n\n在上一章中，我们实现了与 ref 相关的 API．接下来，让我们谈谈 computed．\nhttps://vuejs.org/api/reactivity-core.html#computed\n\nComputed 有两个签名：只读和可写．\n\n```ts\n// 只读\nfunction computed<T>(\n  getter: () => T,\n  // 参见下面的\"Computed 调试\"链接\n  debuggerOptions?: DebuggerOptions,\n): Readonly<Ref<Readonly<T>>>\n\n// 可写\nfunction computed<T>(\n  options: {\n    get: () => T\n    set: (value: T) => void\n  },\n  debuggerOptions?: DebuggerOptions,\n): Ref<T>\n```\n\n官方实现有点复杂，但让我们从一个简单的结构开始．\n\n实现它的最简单方法是每次检索值时触发回调．\n\n```ts\nexport class ComputedRefImpl<T> {\n  constructor(private getter: ComputedGetter<T>) {}\n\n  get value() {\n    return this.getter()\n  }\n\n  set value() {}\n}\n```\n\n然而，这并不是真正的 computed．它只是调用一个函数（这并不是很令人兴奋）．\n\n实际上，我们希望跟踪依赖项并在值更改时重新计算．\n\n为了实现这一点，我们使用一种机制，将 `_dirty` 标志作为调度器作业更新．\n`_dirty` 标志是一个表示值是否需要重新计算的标志．它在被依赖项触发时更新．\n\n以下是它的工作原理示例：\n\n```ts\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined\n  private _value!: T\n  public readonly effect: ReactiveEffect<T>\n  public _dirty = true\n\n  constructor(getter: ComputedGetter<T>) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true\n      }\n    })\n  }\n\n  get value() {\n    trackRefValue(this)\n    if (this._dirty) {\n      this._dirty = false\n      this._value = this.effect.run()\n    }\n    return this._value\n  }\n}\n```\n\nComputed 实际上具有惰性求值的性质，所以值只在第一次读取时重新计算．\n我们将此标志更新为 true，函数被多个依赖项触发，所以我们将其注册为 ReactiveEffect 的调度器．\n\n这是基本流程．在实现时，有几个要注意的点，让我们在下面总结它们．\n\n- 当将 `_dirty` 标志更新为 true 时，触发它拥有的依赖项．\n  ```ts\n  if (!this._dirty) {\n    this._dirty = true\n    triggerRefValue(this)\n  }\n  ```\n- 由于 computed 被归类为 `ref`，将 `__v_isRef` 标记为 true．\n- 如果你想实现 setter，最后实现它．首先，目标是使其可计算．\n\n现在我们准备好了，让我们实现它！如果下面的代码按预期工作，就可以了！（请确保只触发 computed 依赖项！）\n\n```ts\nimport { computed, createApp, h, reactive, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = reactive({ value: 0 })\n    const count2 = reactive({ value: 0 })\n    const double = computed(() => {\n      console.log('computed')\n      return count.value * 2\n    })\n    const doubleDouble = computed(() => {\n      console.log('computed (doubleDouble)')\n      return double.value * 2\n    })\n\n    const countRef = ref(0)\n    const doubleCountRef = computed(() => {\n      console.log('computed (doubleCountRef)')\n      return countRef.value * 2\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('p', {}, [`count2: ${count2.value}`]),\n        h('p', {}, [`double: ${double.value}`]),\n        h('p', {}, [`doubleDouble: ${doubleDouble.value}`]),\n        h('p', {}, [`doubleCountRef: ${doubleCountRef.value}`]),\n        h('button', { onClick: () => count.value++ }, ['update count']),\n        h('button', { onClick: () => count2.value++ }, ['update count2']),\n        h('button', { onClick: () => countRef.value++ }, ['update countRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/050_computed)\n（带 setter）：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/060_computed_setter)\n\n## Watch 的实现\n\nhttps://vuejs.org/api/reactivity-core.html#watch\n\nwatch API 有各种形式．让我们从实现最简单的形式开始，即使用 getter 函数进行监视．\n首先，让我们目标使下面的代码工作．\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    watch(\n      () => state.count,\n      () => alert('state.count was changed!'),\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: () => state.count++ }, ['update state']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nwatch 的实现不在 reactivity 中，而在 runtime-core（apiWatch.ts）中．\n\n它可能看起来有点复杂，因为有各种 API 混合在一起，但如果你缩小范围，实际上非常简单．\n我已经在下面实现了目标 API（watch 函数）的签名，所以请尝试实现它．我相信如果你到目前为止已经掌握了响应式的知识，你可以做到！\n\n```ts\nexport type WatchEffect = (onCleanup: OnCleanup) => void\n\nexport type WatchSource<T = any> = () => T\n\ntype OnCleanup = (cleanupFn: () => void) => void\n\nexport function watch<T>(\n  source: WatchSource<T>,\n  cb: (newValue: T, oldValue: T) => void,\n) {\n  // TODO:\n}\n```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/070_watch)\n\n## watch 的其他 API\n\n一旦你有了基础，就只是扩展的问题．不需要进一步解释．\n\n- 监视 ref\n  ```ts\n  const count = ref(0)\n  watch(count, () => {\n    /** some effects */\n  })\n  ```\n- 监视多个源\n\n  ```ts\n  const count = ref(0)\n  const count2 = ref(0)\n  const count3 = ref(0)\n  watch([count, count2, count3], () => {\n    /** some effects */\n  })\n  ```\n\n- Immediate\n\n  ```ts\n  const count = ref(0)\n  watch(\n    count,\n    () => {\n      /** some effects */\n    },\n    { immediate: true },\n  )\n  ```\n\n- Deep\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(\n    () => state,\n    () => {\n      /** some effects */\n    },\n    { deep: true },\n  )\n  ```\n\n- 响应式对象\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(state, () => {\n    /** some effects */\n  }) // 自动进入深度模式\n  ```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/080_watch_api_extends)\n\n## watchEffect\n\nhttps://vuejs.org/api/reactivity-core.html#watcheffect\n\n使用 watch 实现来实现 watchEffect 很容易．\n\n```ts\nconst count = ref(0)\n\nwatchEffect(() => console.log(count.value))\n// -> 记录 0\n\ncount.value++\n// -> 记录 1\n```\n\n你可以像 immediate 的图像一样实现它．\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/090_watch_effect)\n\n---\n\n※ 清理将在单独的章节中完成．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/30-basic-reactivity-system/030-reactive-proxy-handlers.md",
    "content": "# 各种响应式代理处理器\n\n::: warning\n这里解释的实现基于当前草拟的[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的内容将更新以与其保持一致．\n:::\n\n## 不应该是响应式的对象\n\n现在，让我们解决当前响应式系统的一个问题．  \n首先，尝试运行以下代码．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n如果你检查控制台，你应该看到以下结果：\n\n![Reactive HTML element console output](/figures/30-basic-reactivity-system/reactive-proxy-handlers/reactive-html-element.png)\n\n现在，让我们添加一个焦点函数．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n令人惊讶的是，它抛出了一个错误．\n\n![Focus result in a reactive HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-reactive-html-element.png)\n\n原因是 `document.getElementById` 获得的元素被用来生成 Proxy 本身．\n\n当生成 Proxy 时，值变成 Proxy 而不是原始对象，导致 HTML 元素功能的丢失．\n\n## 在生成响应式代理之前确定对象\n\n确定方法非常简单．使用 `Object.prototype.toString`．\n让我们看看 `Object.prototype.toString` 如何在上面的代码中确定 HTMLInputElement．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value?.toString())\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n![HTMLElement toString result](/figures/30-basic-reactivity-system/reactive-proxy-handlers/element-to-string.png)\n\n这允许我们确定对象的类型．虽然有些硬编码，但让我们概括这个确定函数．\n\n```ts\n// shared/general.ts\nexport const objectToString = Object.prototype.toString // 已在 isMap 和 isSet 中使用\nexport const toTypeString = (value: unknown): string =>\n  objectToString.call(value)\n\n// 这次要添加的函数\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1)\n}\n```\n\n使用 `slice` 的原因是获取 `[Object hoge]` 中对应于 `hoge` 的字符串．\n\n然后，让我们通过使用 `reactive toRawType` 确定对象的类型并进行分支．\n跳过为 HTMLInput 生成 Proxy．\n\n在 reactive.ts 中，获取 rawType 并确定将成为 reactive 目标的对象类型．\n\n```ts\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value)\n    ? TargetType.INVALID\n    : targetTypeMap(toRawType(value))\n}\n```\n\n```ts\nexport function reactive<T extends object>(target: T): T {\n  const targetType = getTargetType(target)\n  if (targetType === TargetType.INVALID) {\n    return target\n  }\n\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\n现在，焦点代码应该工作了！\n\n![Focus result in a plain HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-element.png)\n\n## 实现 TemplateRefs\n\n现在我们可以将 HTML 元素放入 Ref 中，让我们实现 TemplateRef．\n\nRef 可以通过使用 ref 属性来引用模板．\n\nhttps://vuejs.org/guide/essentials/template-refs.html\n\n目标是使以下代码工作：\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { ref: inputRef }, []),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n如果你已经走到这一步，你可能已经看到如何实现它．\n是的，只需将 ref 添加到 VNode 并在渲染期间注入值．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  key: string | number | symbol | null\n  ref: Ref | null // 这个\n  // .\n  // .\n}\n```\n\n在原始实现中，它被称为 `setRef`．找到它，阅读它，并实现它！\n在原始实现中，它更复杂，ref 是一个数组并且可以通过 `$ref` 访问，但现在，让我们目标使上面的代码工作．\n\n顺便说一下，如果它是一个组件，将组件的 `setupContext` 分配给 ref．  \n（注意：实际上，你应该传递组件的代理，但它还没有实现，所以我们现在使用 `setupContext`．）\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst Child = {\n  setup() {\n    const action = () => alert('clicked!')\n    return { action }\n  },\n\n  template: `<button @click=\"action\">action (child)</button>`,\n}\n\nconst app = createApp({\n  setup() {\n    const childRef = ref<any>(null)\n    const childAction = () => {\n      childRef.value?.action()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('div', {}, [\n          h(Child, { ref: childRef }, []),\n          h('button', { onClick: childAction }, ['action (parent)']),\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/110_template_refs)\n\n## 处理具有变化键的对象\n\n实际上，当前的实现无法处理具有变化键的对象．\n这也包括数组．\n换句话说，以下组件无法正常工作：\n\n```ts\nconst App = {\n  setup() {\n    const array = ref<number[]>([])\n    const mutateArray = () => {\n      array.value.push(Date.now()) // 即使调用这个也不会触发 effect（set 的键是 \"0\"）\n    }\n\n    const record = reactive<Record<string, number>>({})\n    const mutateRecord = () => {\n      record[Date.now().toString()] = Date.now() // 即使键改变也不会触发 effect\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`array: ${JSON.stringify(array.value)}`]),\n        h('button', { onClick: mutateArray }, ['update array']),\n\n        h('p', {}, [`record: ${JSON.stringify(record)}`]),\n        h('button', { onClick: mutateRecord }, ['update record']),\n      ])\n  },\n}\n```\n\n我们如何解决这个问题？\n\n### 对于数组\n\n数组本质上是对象，所以当添加新元素时，其索引作为键传递给 Proxy 的 `set` 处理器．\n\n```ts\nconst p = new Proxy([], {\n  set(target, key, value, receiver) {\n    console.log(key) // ※\n    Reflect.set(target, key, value, receiver)\n    return true\n  },\n})\n\np.push(42) // 0\n```\n\n然而，我们无法单独跟踪这些键中的每一个．\n因此，我们可以跟踪数组的 `length` 来触发数组的变化．\n\n值得注意的是，`length` 已经被跟踪了．\n\n如果你在浏览器或类似环境中执行以下代码，你会看到当使用 `JSON.stringify` 字符串化数组时会调用 `length`．\n\n```ts\nconst data = new Proxy([], {\n  get(target, key) {\n    console.log('get!', key)\n    return Reflect.get(target, key)\n  },\n})\n\nJSON.stringify(data)\n// get! length\n// get! toJSON\n```\n\n换句话说，`length` 已经注册了一个 effect．所以，我们需要做的就是提取这个 effect 并在设置索引时触发它．\n\n如果键被确定为索引，我们触发 `length` 的 effect．\n当然，可能还有其他依赖项，所以我们将它们提取到一个名为 `deps` 的数组中并一起触发 effect．\n\n```ts\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  // 这个\n  if (isIntegerKey(key)) {\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n```ts\n// shared/general.ts\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) &&\n  key !== 'NaN' &&\n  key[0] !== '-' &&\n  '' + parseInt(key, 10) === key\n```\n\n现在，数组应该正常工作了．\n\n### 对于对象（记录）\n\n接下来，让我们考虑对象．与数组不同，对象没有 `length` 属性．\n\n我们可以在这里做一个小修改．\n我们可以准备一个名为 `ITERATE_KEY` 的符号，并以类似于数组的 `length` 属性的方式使用它．\n你可能不理解我的意思，但由于 `depsMap` 只是一个 Map，使用我们定义的符号作为键没有问题．\n\n操作顺序与数组略有不同，但让我们从考虑 `trigger` 函数开始．\n我们可以实现它，就好像有一个注册了 effect 的 `ITERATE_KEY`．\n\n```ts\nexport const ITERATE_KEY = Symbol()\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  if (!isArray(target)) {\n    // 如果不是数组，触发用 ITERATE_KEY 注册的 effect\n    deps.push(depsMap.get(ITERATE_KEY))\n  } else if (isIntegerKey(key)) {\n    // 向数组添加新索引 -> length 改变\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n问题是如何跟踪 `ITERATE_KEY` 的 effect．\n\n在这里，我们可以使用 `ownKeys` Proxy 处理器．\n\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys\n\n`ownKeys` 被 `Object.keys()` 或 `Reflect.ownKeys()` 等函数调用，但它也被 `JSON.stringify` 调用．\n\n你可以通过在浏览器或类似环境中运行以下代码来确认这一点：\n\n```ts\nconst data = new Proxy(\n  {},\n  {\n    get(target, key) {\n      return Reflect.get(target, key)\n    },\n    ownKeys(target) {\n      console.log('ownKeys!!!')\n      return Reflect.ownKeys(target)\n    },\n  },\n)\n\nJSON.stringify(data)\n```\n\n我们可以使用这个来跟踪 `ITERATE_KEY`．\n对于数组，我们不需要它，所以我们可以简单地跟踪 `length`．\n\n```ts\nexport const mutableHandlers: ProxyHandler<object> = {\n  // .\n  // .\n  ownKeys(target) {\n    track(target, isArray(target) ? 'length' : ITERATE_KEY)\n    return Reflect.ownKeys(target)\n  },\n}\n```\n\n现在，我们应该能够处理具有变化键的对象了！\n\n## 支持基于集合的内置对象\n\n目前，在查看 reactive.ts 的实现时，它只针对 Object 和 Array．\n\n```ts\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n```\n\n在 Vue.js 中，除了这些，它还支持 Map，Set，WeakMap 和 WeakSet．\n\nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/reactive.ts#L43C1-L56C2\n\n这些对象被实现为单独的 Proxy 处理器．它被称为 `collectionHandlers`．\n\n在这里，我们将实现这个 `collectionHandlers` 并目标使以下代码工作．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ map: new Map(), set: new Set() })\n\n    return () =>\n      h('div', {}, [\n        h('h1', {}, [`ReactiveCollection`]),\n\n        h('p', {}, [\n          `map (${state.map.size}): ${JSON.stringify([...state.map])}`,\n        ]),\n        h('button', { onClick: () => state.map.set(Date.now(), 'item') }, [\n          'update map',\n        ]),\n\n        h('p', {}, [\n          `set (${state.set.size}): ${JSON.stringify([...state.set])}`,\n        ]),\n        h('button', { onClick: () => state.set.add('item') }, ['update set']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n在 `collectionHandlers` 中，我们为 add，set 和 delete 等方法实现处理器．\n这些的实现可以在 `collectionHandlers.ts` 中找到．\nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/collectionHandlers.ts#L0-L1\n通过确定 `TargetType`，如果它是集合类型，我们基于这个处理器为 `h` 生成 Proxy．\n让我们实际实现它！\n\n需要注意的一点是，当将目标本身传递给 Reflect 的接收器时，如果目标本身设置了 Proxy，可能会导致无限循环．\n为了避免这种情况，我们改变结构，将原始数据附加到目标，当实现 Proxy 处理器时，我们修改它以在这个原始数据上操作．\n\n```ts\nexport const enum ReactiveFlags {\n  RAW = '__v_raw',\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any\n}\n```\n\n严格来说，这个实现也应该为正常的响应式处理器完成，但为了最小化不必要的解释并且因为到目前为止没有问题，所以省略了．\n让我们尝试实现它，如果进入 getter 的键是 `ReactiveFlags.RAW`，它返回原始数据而不是 Proxy．\n\n与此同时，我们还实现了一个名为 `toRaw` 的函数，它递归地从目标检索原始数据并最终获得处于原始状态的数据．\n\n```ts\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW]\n  return raw ? toRaw(raw) : observed\n}\n```\n\n顺便说一下，这个 `toRaw` 函数也作为 API 函数提供．\n\nhttps://vuejs.org/api/reactivity-advanced.html#toraw\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/120_proxy_handler_improvement)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/30-basic-reactivity-system/040-effect-scope.md",
    "content": "# Effect 清理和 Effect 作用域\n\n::: warning\n这里解释的实现基于当前草拟的[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的内容将更新以与其保持一致．\n:::\n\n## ReactiveEffect 的清理\n\n到目前为止，我们还没有清理注册的 effect．让我们为 ReactiveEffect 添加清理处理．\n\n在 ReactiveEffect 中实现一个名为 `stop` 的方法．  \n为 ReactiveEffect 添加一个标志来指示它是否处于活动状态，在 `stop` 方法中，将其切换为 `false` 同时删除依赖项．\n\n```ts\nexport class ReactiveEffect<T = any> {\n  active = true // 添加\n  //.\n  //.\n  //.\n  stop() {\n    if (this.active) {\n      this.active = false\n    }\n  }\n}\n```\n\n有了这个基本实现，我们需要做的就是在执行 `stop` 方法时删除所有依赖项．  \n此外，让我们添加钩子的实现，允许我们注册在清理期间要执行的处理，以及当 `activeEffect` 是自身时的处理．\n\n```ts\nexport class ReactiveEffect<T = any> {\n  private deferStop?: boolean // 添加\n  onStop?: () => void // 添加\n  parent: ReactiveEffect | undefined = undefined // 添加（在 finally 中引用）\n\n  run() {\n    if (!this.active) {\n      return this.fn() // 如果 active 为 false，简单地执行函数\n    }\n\n    try {\n      this.parent = activeEffect\n      activeEffect = this\n      const res = this.fn()\n      return res\n    } finally {\n      activeEffect = this.parent\n      this.parent = undefined\n      if (this.deferStop) {\n        this.stop()\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      // 如果 activeEffect 是自身，设置标志在 run 完成后停止\n      this.deferStop = true\n    } else if (this.active) {\n      // ...\n      if (this.onStop) {\n        this.onStop() // 执行注册的钩子\n      }\n      // ...\n    }\n  }\n}\n```\n\n现在我们已经为 ReactiveEffect 添加了清理处理，让我们也为 watch 实现清理函数．\n\n如果以下代码工作，就可以了．\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    const unwatch = watch(\n      () => state.count,\n      (newValue, oldValue, cleanup) => {\n        alert(`New value: ${newValue}, old value: ${oldValue}`)\n        cleanup(() => alert('Clean Up!'))\n      },\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, [`increment`]),\n        h('button', { onClick: unwatch }, [`unwatch`]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/130_cleanup_effects)\n\n## 什么是 Effect 作用域\n\n现在我们可以清理 effect，我们希望在组件卸载时清理不必要的 effect．然而，收集大量的 effect（无论是 watch 还是 computed）有点麻烦．如果我们尝试直接实现它，它会看起来像这样：\n\n```ts\nlet disposables = []\n\nconst counter = ref(0)\n\nconst doubled = computed(() => counter.value * 2)\ndisposables.push(() => stop(doubled.effect))\n\nconst stopWatch = watchEffect(() => console.log(`counter: ${counter.value}`))\ndisposables.push(stopWatch)\n```\n\n```ts\n// 清理 effect\ndisposables.forEach(f => f())\ndisposables = []\n```\n\n这种管理方式很麻烦且容易出错．\n\n因此，Vue 有一个称为 EffectScope 的机制．  \nhttps://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md\n\n想法是每个实例有一个 EffectScope，具体来说，它有以下接口：\n\n```ts\nconst scope = effectScope()\n\nscope.run(() => {\n  const doubled = computed(() => counter.value * 2)\n\n  watch(doubled, () => console.log(doubled.value))\n\n  watchEffect(() => console.log('Count: ', doubled.value))\n})\n\n// 处理作用域中的所有 effect\nscope.stop()\n```\n\n引用自：https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md#basic-example\n\n这个 EffectScope 也作为面向用户的 API 公开．  \nhttps://vuejs.org/api/reactivity-advanced.html#effectscope\n\n## EffectScope 的实现\n\n如前所述，我们将每个实例有一个 EffectScope．\n\n```ts\nexport interface ComponentInternalInstance {\n  scope: EffectScope\n}\n```\n\n当组件卸载时，我们停止收集的 effect．\n\n```ts\nconst unmountComponent = (...) => {\n  // .\n  // .\n  const { scope } = instance;\n  scope.stop();\n  // .\n  // .\n}\n```\n\nEffectScope 的结构如下：它有一个名为 `activeEffectScope` 的变量，指向当前活动的 EffectScope，并使用在 EffectScope 中实现的 `on/off/run/stop` 方法管理其状态．  \n`on/off` 方法将自身提升为 `activeEffectScope` 或恢复提升状态（返回到原始 EffectScope）．  \n当创建 ReactiveEffect 时，它在 `activeEffectScope` 中注册．\n\n由于可能有点难以理解，如果我们在源代码中写出图像，\n\n```ts\ninstance.scope.on()\n\n/** 创建一些 ReactiveEffect，如 computed 或 watch */\nsetup()\n\ninstance.scope.off()\n```\n\n通过这种方式，我们可以在实例的 EffectScope 中收集生成的 effect．  \n然后，当触发此 effect 的 `stop` 方法时，我们可以清理所有 effect．\n\n你应该已经理解了基本原理，所以让我们在阅读源代码的同时尝试实现它！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/140_effect_scope)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/30-basic-reactivity-system/050-other-apis.md",
    "content": "# 其他响应式 API\n\n::: warning\n这里解释的实现基于当前草拟的[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[响应式优化](/zh-cn/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的内容将更新以与其保持一致．\n:::\n\n## 让我们实现其他响应式 API！\n\n- customRef\n- readonly\n- shallowReactive\n- unref\n- isProxy\n- isReactive\n- isReadonly\n\n如果你已经走到这一步，你应该能够通过阅读源代码来实现它们，而无需任何解释！\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/150_other_apis)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/40-basic-component-system/010-lifecycle-hooks.md",
    "content": "# 生命周期钩子（基础组件系统开始）\n\n## 让我们实现生命周期钩子\n\n实现生命周期钩子非常简单．\n你只需要在 ComponentInternalInstance 中注册函数，并在渲染期间的指定时机执行它们．\nAPI 本身将在 runtime-core/apiLifecycle.ts 中实现．\n\n需要注意的一点是，你需要考虑 onMounted/onUnmounted/onUpdated 的调度．\n注册的函数应该在挂载，卸载和更新完全完成后执行．\n\n因此，我们将在调度器中实现一种名为\"post\"的新队列类型．这是在现有队列刷新完成后才会被刷新的队列．\n图像 ↓\n\n```ts\nconst queue: SchedulerJob[] = [] // 现有实现\nconst pendingPostFlushCbs: SchedulerJob[] = [] // 这次要创建的新队列\n\nfunction queueFlush() {\n  queue.forEach(job => job())\n  flushPostFlushCbs() // 在队列刷新后刷新\n}\n```\n\n同时，通过这个，让我们实现一个入队到 pendingPostFlushCbs 的 API．\n并且让我们使用它将渲染器中的 effect 入队到 pendingPostFlushCbs．\n\n这次要支持的生命周期钩子：\n\n- onMounted\n- onUpdated\n- onUnmounted\n- onBeforeMount\n- onBeforeUpdate\n- onBeforeUnmount\n\n让我们实现它，目标是使以下代码工作！\n\n```ts\nimport {\n  createApp,\n  h,\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  ref,\n} from 'chibivue'\n\nconst Child = {\n  setup() {\n    const count = ref(0)\n    onBeforeMount(() => {\n      console.log('onBeforeMount')\n    })\n\n    onUnmounted(() => {\n      console.log('onUnmounted')\n    })\n\n    onBeforeUnmount(() => {\n      console.log('onBeforeUnmount')\n    })\n\n    onBeforeUpdate(() => {\n      console.log('onBeforeUpdate')\n    })\n\n    onUpdated(() => {\n      console.log('onUpdated')\n    })\n\n    onMounted(() => {\n      console.log('onMounted')\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const mountFlag = ref(true)\n\n    return () =>\n      h('div', {}, [\n        h('button', { onClick: () => (mountFlag.value = !mountFlag.value) }, [\n          'toggle',\n        ]),\n        mountFlag.value ? h(Child, {}, []) : h('p', {}, ['unmounted']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/010_lifecycle_hooks)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/40-basic-component-system/020-provide-inject.md",
    "content": "# Provide/Inject 的实现\n\n## 让我们实现 Provide/Inject\n\n这是 Provide 和 Inject 的实现．实现也相当简单．  \n基本概念是在 ComponentInternalInstance 中有一个地方来存储提供的数据（provides），并保持父组件的实例来继承数据．\n\n需要注意的一点是，provide 有两个入口点．一个是在组件的 setup 期间，这很容易想象，  \n另一个是在 App 上调用 provide 时．\n\n```ts\nconst app = createApp({\n  setup() {\n    //.\n    //.\n    //.\n    provide('key', someValue) // 这是从组件调用 provide 的情况\n    //.\n    //.\n  },\n})\n\napp.provide('key2', someValue2) // 在 App 上提供\n```\n\n现在，我们应该在哪里存储 app 提供的内容？由于 app 不是组件，这是一个问题．\n\n为了给你答案，让我们说 app 实例有一个名为 AppContext 的对象，我们将在其中存储 provides 对象．\n\n将来，我们将向这个 AppContext 添加全局组件和自定义指令设置．\n\n现在我们已经解释了到目前为止的一切，让我们实现代码，使其按以下方式工作！\n\n※ 假设的签名\n\n```ts\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n)\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T\n```\n\n```ts\nconst Child = {\n  setup() {\n    const rootState = inject<{ count: number }>('RootState')\n    const logger = inject(LoggerKey)\n\n    const action = () => {\n      rootState && rootState.count++\n      logger?.('Hello from Child.')\n    }\n\n    return () => h('button', { onClick: action }, ['action'])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 1 })\n    provide('RootState', state)\n\n    return () =>\n      h('div', {}, [h('p', {}, [`${state.count}`]), h(Child, {}, [])])\n  },\n})\n\ntype Logger = (...args: any) => void\nconst LoggerKey = Symbol() as InjectionKey<Logger>\n\napp.provide(LoggerKey, window.console.log)\n```\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/020_provide_inject)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/40-basic-component-system/030-component-proxy-setup-context.md",
    "content": "# 组件的代理和 setupContext\n\n## 组件的代理\n\n组件有一个重要概念叫做代理（Proxy）．  \n简单来说，它是一个允许访问组件实例数据（公共属性）的代理．\n代理结合了 setup 的结果（状态，函数），data，props 和其他访问．\n\n让我们考虑以下代码（包括在 chibivue 中未实现的部分，所以请将其视为常规 Vue）：\n\n```vue\n<script>\nexport default defineComponent({\n  props: { parentCount: { type: Number, default: 0 } },\n  data() {\n    return { dataState: { count: 0 } }\n  },\n  methods: {\n    incrementData() {\n      this.dataState.count++\n    },\n  },\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return { state, increment }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <p>count (parent): {{ parentCount }}</p>\n\n    <br />\n\n    <p>count (data): {{ dataState.count }}</p>\n    <button @click=\"incrementData\">increment (data)</button>\n\n    <br />\n\n    <p>count: {{ state.count }}</p>\n    <button @click=\"increment\">increment</button>\n  </div>\n</template>\n```\n\n这段代码工作正常，但它是如何绑定到模板的？\n\n让我们考虑另一个例子．\n\n```vue\n<script setup>\nconst ChildRef = ref()\n\n// 访问组件的方法和数据\n// ChildRef.value?.incrementData\n// ChildRef.value?.increment\n</script>\n\n<template>\n  <!-- Child 是前面提到的组件 -->\n  <Child :ref=\"ChildRef\" />\n</template>\n```\n\n在这种情况下，你可以通过 ref 访问组件的信息．\n\n为了实现这一点，ComponentInternalInstance 有一个名为 proxy 的属性，它保存用于数据访问的代理．\n\n换句话说，模板（渲染函数）和 ref 引用 instance.proxy．\n\n```ts\ninterface ComponentInternalInstance {\n  proxy: ComponentPublicInstance | null\n}\n```\n\n这个代理的实现是使用 Proxy 完成的，大致如下：\n\n```ts\ninstance.proxy = instance.proxy = new Proxy(\n  instance,\n  PublicInstanceProxyHandlers,\n)\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get(instance: ComponentRenderContext, key: string) {\n    const { setupState, ctx, props } = instance\n\n    // 根据键按 setupState -> props -> ctx 的顺序检查，如果存在则返回值\n  },\n}\n```\n\n让我们实现这个代理！\n\n一旦实现，让我们修改代码以将此代理传递给渲染函数和 ref．\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/030_component_proxy)\n\n※ 顺便说一下，我还实现了 defineComponent 和相关类型检查的实现（这允许我们推断代理数据的类型）．\n\n![Component type inference result](/figures/40-basic-component-system/component-proxy-setup-context/infer-component-types.png)\n\n## setupContext\n\nhttps://ja.vuejs.org/api/composition-api-setup.html#setup-context\n\nVue 有一个名为 setupContext 的概念．这是在 setup 函数中暴露的上下文，包括 emit 和 expose．\n\n目前，emit 正在工作，但实现有些粗糙．\n\n```ts\nconst setupResult = component.setup(instance.props, {\n  emit: instance.emit,\n})\n```\n\n让我们正确定义 SetupContext 接口，并将其表示为实例持有的对象．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupContext: SetupContext | null // 添加\n}\n\nexport type SetupContext = {\n  emit: (e: string, ...args: any[]) => void\n}\n```\n\n然后，在创建实例时，生成 setupContext 并在执行 setup 函数时将此对象作为第二个参数传递．\n\n## expose\n\n一旦你到达这一点，让我们尝试实现除 emit 之外的 SetupContext．  \n作为这次的例子，让我们实现 expose．\n\nexpose 是一个允许你明确定义公共属性的函数．  \n让我们目标实现如下的开发者接口：\n\n```ts\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const log = () => {\n      console.log(\n        child.value.count,\n        child.value.count2, // 无法访问\n        child2.value.count,\n        child2.value.count2,\n      )\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: log }, ['log']),\n      ])\n  },\n})\n```\n\n对于不使用 expose 的组件，默认情况下一切仍然是公共的．\n\n作为方向，让我们在实例内部有一个名为 `exposed` 的对象，如果这里设置了值，我们将把这个对象传递给 templateRef 的 ref．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  exposed: Record<string, any> | null // 添加\n}\n```\n\n让我们实现 expose 函数，以便可以在这里注册对象．\n\n## ProxyRefs\n\n在本章中，我们实现了 proxy 和 exposedProxy，但实际上与原始 Vue 有一些差异．\n那就是\"ref 被解包\"．（在 proxy 的情况下，setupState 具有此属性而不是 proxy．）\n\n这些是用 ProxyRefs 实现的，处理器在名为 `shallowUnwrapHandlers` 的名称下实现．\n这允许我们在编写模板或处理代理时消除 ref 特定值的冗余．\n\n```ts\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key]\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value\n      return true\n    } else {\n      return Reflect.set(target, key, value, receiver)\n    }\n  },\n}\n```\n\n```vue\n<template>\n  <!-- <p>{{ count.value }}</p>  不需要这样写 -->\n  <p>{{ count }}</p>\n</template>\n```\n\n如果你实现到这一点，以下代码应该工作．\n\n```ts\nimport { createApp, defineComponent, h, ref } from 'chibivue'\n\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>child {{ count }} {{ count2 }}</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>child2 {{ count }} {{ count2 }}</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const increment = () => {\n      child.value.count++\n      child.value.count2++ // 无法访问\n      child2.value.count++\n      child2.value.count2++\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n## 模板绑定和 with 语句\n\n实际上，本章的更改存在一个问题．\n让我们尝试运行以下代码：\n\n```ts\nconst Child2 = {\n  setup() {\n    const state = reactive({ count: 0 })\n    return { state }\n  },\n  template: `<p>child2 count: {{ state.count }}</p>`,\n}\n```\n\n这只是一个简单的代码，但它不工作．\n它抱怨 state 未定义．\n\n![state is not defined runtime error](/figures/40-basic-component-system/component-proxy-setup-context/state-is-not-defined.png)\n\n原因是当将代理作为参数传递给 with 语句时，必须定义 has．\n\n[使用 with 语句和代理创建动态命名空间 (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with#creating_dynamic_namespaces_using_the_with_statement_and_a_proxy)\n\n所以让我们在 PublicInstanceProxyHandlers 中实现 has．\n如果键存在于 setupState，props 或 ctx 中，它应该返回 true．\n\n```ts\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  // .\n  // .\n  // .\n  has(\n    { _: { setupState, ctx, propsOptions } }: ComponentRenderContext,\n    key: string,\n  ) {\n    let normalizedProps\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    )\n  },\n}\n```\n\n如果它工作正常，应该可以正常工作！\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/040_setup_context)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/40-basic-component-system/040-component-slot.md",
    "content": "# 插槽\n\n## 默认插槽的实现\n\nVue 有一个名为插槽的功能，包括三种类型：默认插槽，命名插槽和作用域插槽．\nhttps://vuejs.org/guide/components/slots.html#slots\n\n这次，我们将实现其中的默认插槽．\n期望的开发者接口如下：\n\nhttps://vuejs.org/guide/extras/render-function.html#passing-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () => h('div', {}, [slots.default()])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () => h(MyComponent, {}, () => 'hello')\n  },\n})\n```\n\n机制很简单．在插槽定义端，我们确保将 slots 作为 setupContext 接收，在使用端用 h 函数渲染组件时，我们只需将渲染函数作为 children 传递．\n也许对每个人来说最熟悉的用法是在 SFC 的模板中放置一个 slot 元素，但这需要实现一个单独的模板编译器，所以我们这次省略它．（我们将在基础模板编译器部分介绍它．）\n\n像往常一样，向实例添加一个可以保存插槽的属性，并使用 createSetupContext 将其作为 SetupContext 混合．\n修改 h 函数，使其可以接收渲染函数作为第三个参数，而不仅仅是数组，如果传递了渲染函数，在生成实例时将其设置为组件实例的默认插槽．\n让我们先实现到这一点！\n\n（由于在 children 中实现了 normalize，ShapeFlags 已经略有更改．）\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/050_component_slot)\n\n## 命名插槽/作用域插槽的实现\n\n这是默认插槽的扩展．\n这次，让我们尝试传递一个对象而不是函数．\n\n对于作用域插槽，你只需要定义渲染函数的参数．\n如你所见，当使用渲染函数时，似乎没有必要区分作用域插槽．\n没错，插槽的本质只是一个回调函数，API 作为作用域插槽提供以允许向其传递参数．\n当然，我们将在基础模板编译器部分实现一个可以处理作用域插槽的编译器，但它们将被转换为这些形式．\n\nhttps://vuejs.org/guide/components/slots.html#scoped-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () =>\n      h('div', {}, [\n        slots.default?.(),\n        h('br', {}, []),\n        slots.myNamedSlot?.(),\n        h('br', {}, []),\n        slots.myScopedSlot2?.({ message: 'hello!' }),\n      ])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h(\n        MyComponent,\n        {},\n        {\n          default: () => 'hello',\n          myNamedSlot: () => 'hello2',\n          myScopedSlot2: (scope: { message: string }) =>\n            `message: ${scope.message}`,\n        },\n      )\n  },\n})\n```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/060_slot_extend)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/40-basic-component-system/050-options-api.md",
    "content": "# 支持 Options API\n\n## Options API\n\n到目前为止，我们已经能够使用 Composition API 实现很多功能，但让我们也支持 Options API．\n\n在本书中，我们在 Options API 中支持以下内容：\n\n- props\n- data\n- computed\n- method\n- watch\n- slot\n- lifecycle\n  - onMounted\n  - onUpdated\n  - onUnmounted\n  - onBeforeMount\n  - onBeforeUpdate\n  - onBeforeUnmount\n- provide/inject\n- $el\n- $data\n- $props\n- $slots\n- $parent\n- $emit\n- $forceUpdate\n- $nextTick\n\n作为实现方法，让我们在 \"componentOptions.ts\" 中准备一个名为 \"applyOptions\" 的函数，并在 \"setupComponent\" 的末尾执行它．\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n  // ↑ 这是现有实现\n\n  setCurrentInstance(instance)\n  applyOptions(instance)\n  unsetCurrentInstance()\n}\n```\n\n在 Options API 中，开发者接口经常处理 \"this\"．\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { message: 'hello' }\n  },\n\n  methods: {\n    greet() {\n      console.log(this.message) // 像这样\n    },\n  },\n})\n```\n\n在内部，\"this\" 在 Options API 中指向组件的代理，在应用选项时，这个代理被绑定．\n\n实现示例 ↓\n\n```ts\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance\n  const publicThis = instance.proxy! as any\n  const ctx = instance.ctx\n\n  const { methods } = options\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key]\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis)\n      }\n    }\n  }\n}\n```\n\n基本上，如果你使用这个原理逐一实现它们，应该不会太困难．\n\n如果你想让 \"data\" 变成响应式，在这里调用 \"reactive\" 函数，如果你想计算，在这里调用 \"computed\" 函数．（\"provide/inject\" 也是如此）\n\n由于在执行 \"applyOptions\" 之前通过 \"setCurrentInstance\" 设置了实例，你可以以相同的方式调用到目前为止一直使用的 API（Composition API）．\n\n关于以 \"$\" 开头的属性，它们由 \"componentPublicInstance\" 的实现控制．\"PublicInstanceProxyHandlers\" 中的 getter 控制它们．\n\n## 为 Options API 添加类型\n\n在功能上，按照上述描述实现是可以的，但为 Options API 添加类型有点复杂．\n\n在本书中，我们为 Options API 支持基本类型．\n\n困难的地方在于 \"this\" 的类型根据用户对每个选项的定义而改变．例如，如果你在 \"data\" 选项中定义一个名为 \"count\" 的 \"number\" 类型属性，你希望 \"computed\" 或 \"method\" 中的 \"this\" 推断出 \"count: number\"．\n\n当然，这不仅适用于 \"data\"，也适用于在 \"computed\" 或 \"methods\" 中定义的那些．\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { count: 0 }\n  },\n\n  methods: {\n    myMethod() {\n      this.count // number\n      this.myComputed // number\n    },\n  },\n\n  computed: {\n    myComputed() {\n      return this.count // number\n    },\n  },\n})\n```\n\n为了实现这一点，你需要实现一个有些复杂的类型拼图（具有许多泛型的中继）．\n\n从 \"defineComponent\" 的类型开始，我们实现几种类型来中继到 \"ComponentOptions\" 和 \"ComponentPublicInstance\"．\n\n在这里，让我们专注于 \"data\" 和 \"methods\" 进行解释．\n\n首先，通常的 \"ComponentOptions\" 类型．我们用泛型扩展它以接受 \"data\" 和 \"methods\" 的类型作为参数，\"D\" 和 \"M\"．\n\n```ts\nexport type ComponentOptions<\n  D = {},\n  M extends MethodOptions = MethodOptions\n> = {\n  data?: () => D;,\n  methods?: M;\n};\n\ninterface MethodOptions {\n  [key: string]: Function;\n}\n```\n\n到目前为止，应该不会太困难．这是可以应用于 \"defineComponent\" 参数的类型．  \n当然，在 \"defineComponent\" 中，我们也接受 \"D\" 和 \"M\" 来中继用户定义的类型．这允许我们中继用户定义的类型．\n\n```ts\nexport function defineComponent<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n>(options: ComponentOptions<D, M>) {}\n```\n\n问题是在 \"methods\" 中处理 \"this\" 时如何将 \"D\" 与 \"this\" 混合（如何使 \"this.count\" 这样的推断成为可能）．\n\n首先，\"D\" 和 \"M\" 被合并到 \"ComponentPublicInstance\" 中（合并到代理中）．这可以理解如下（用泛型扩展）．\n\n```ts\ntype ComponentPublicInstance<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n> = {\n  /** 公共实例具有的各种类型 */\n} & D &\n  M\n```\n\n一旦我们有了这个，我们就将实例类型混合到 \"ComponentOptions\" 中的 \"this\" 中．\n\n```ts\ntype ComponentOptions<D = {}, M extends MethodOptions = MethodOptions> = {\n  data?: () => D\n  methods?: M\n} & ThisType<ComponentPublicInstance<D, M>>\n```\n\n通过这样做，我们可以从选项中的 \"this\" 推断在 \"data\" 和 \"method\" 中定义的属性．\n\n在实践中，我们需要推断各种类型，如 \"props\"，\"computed\" 和 \"inject\"，但基本原理是相同的．  \n乍一看，你可能会被许多泛型和类型转换（如从 \"inject\" 中仅提取 \"key\"）所压倒，但如果你冷静下来并回到原理，你应该没问题．  \n在本书的代码中，受原始 Vue 的启发，我们用 \"CreateComponentPublicInstance\" 进一步抽象了一步，并实现了一个名为 \"ComponentPublicInstanceConstructor\" 的类型，但不要太担心．（如果你感兴趣，你也可以阅读它！）\n\n到此为止的源代码：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/070_options_api)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/010-transform.md",
    "content": "# 转换器和代码生成重构的实现（基础模板编译器部门开始）\n\n## 现有实现的回顾\n\n现在，让我们从最小示例部门停下的地方开始更认真地实现模板编译器．距离我们上次处理它已经有一段时间了，所以让我们回顾一下当前的实现．主要关键词是 Parse，AST 和 Codegen．\n\n![Minimum compiler pipeline](/figures/50-basic-template-compiler/transform/basic-compiler-pipeline.svg)\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  const code = generate(ast, option)\n  return code\n}\n```\n\n实际上，这个配置与原始配置略有不同．让我们看看原始代码．\n\nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/compile.ts#L61\n\n你能理解吗...？\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  transform(ast)\n  const code = generate(ast, option)\n  return code\n}\n```\n\n就是这样．\n\n这次，我们将实现 `transform` 函数．\n\n![Compiler pipeline with transformer](/figures/50-basic-template-compiler/transform/compiler-pipeline-with-transformer.svg)\n\n## 什么是 Transform？\n\n正如你从上面的代码中可以想象的那样，通过解析获得的 AST 被 `transform` 函数以某种方式转换．\n\n你可以通过阅读这个来了解．  \nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/ast.ts#L43C1-L51C23\n\n这个 VNODE_CALL 和以 JS 开头的名称的 AST 代码是我们这次要处理的．\nVue.js 的模板编译器分为两部分：表示解析模板结果的 AST 和表示生成代码的 AST．\n我们当前的实现只处理前一个 AST．\n\n让我们考虑将模板 `<p>hello</p>` 作为输入的情况．\n\n首先，通过解析生成以下 AST．这与现有实现相同．\n\n```ts\ninterface ElementNode {\n  tag: string\n  props: object /** 省略 */\n  children: (ElementNode | TextNode | InterpolationNode)[]\n}\n\ninterface TextNode {\n  content: string\n}\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {},\n  \"children\": [{ \"content\": \"hello\" }]\n}\n```\n\n至于\"表示生成代码的 AST\"，让我们考虑应该生成什么样的代码．\n我认为应该是这样的：\n\n```ts\nh('p', {}, ['hello'])\n```\n\n这是表示生成的 JavaScript 代码的 AST．\n换句话说，它是一个表示用于生成应该生成的代码的 AST 的对象．\n\n```ts\ninterface VNodeCall {\n  tag: string\n  props: PropsExpression\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode // single text child\n    | undefined\n}\n\ntype PropsExpression = ObjectExpression | CallExpression | ExpressionNode\ntype TemplateChildNode = ElementNode | InterpolationNode | TextNode\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {\n    \"type\": \"ObjectExpression\",\n    \"properties\": []\n  },\n  \"children\": { \"content\": \"hello\" }\n}\n```\n\n通过这种方式，表示由 Codegen 生成的代码的 AST 被表达．\n你可能在这一点上感觉不到分离它们的必要性，但在将来实现指令时会很有用．\n通过分离专注于输入的 AST 和专注于输出的 AST，我们可以使用称为 `transform` 的函数执行从 `input AST -> output AST` 的转换．\n\n## Codegen 节点\n\n现在我们已经掌握了流程，让我们确认我们将处理什么样的节点（我们想要转换什么样的节点）．我将在枚举它们并提供注释的同时进行解释．请参考源代码获取准确信息，因为某些部分被省略了．\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[]\n}\n\n// 这表示调用 h 函数的表达式。\n// 它假设类似 `h(\"p\", { class: 'message'}, [\"hello\"])` 的东西。\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: ObjectExpression | undefined // 注意：在源代码中实现为 PropsExpression（用于未来扩展）\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode\n\n// 这表示一个 JavaScript 对象。它用于 VNodeCall 的 props 等。\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION\n  properties: Array<Property>\n}\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY\n  key: ExpressionNode\n  value: JSChildNode\n}\n\n// 这表示一个 JavaScript 数组。它用于 VNodeCall 的 children 等。\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION\n  elements: Array<string | Node>\n}\n```\n\n## 转换器设计\n\n在实现转换器之前，让我们谈谈设计．首先，重要的是要注意有两种类型的转换器：NodeTransform 和 DirectiveTransform．这些分别用于转换节点和指令，并采用以下接口．\n\n```ts\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[]\n\n// TODO:\n// export type DirectiveTransform = (\n//   dir: DirectiveNode,\n//   node: ElementNode,\n//   context: TransformContext,\n// ) => DirectiveTransformResult;\nexport type DirectiveTransform = Function\n```\n\nDirectiveTransform 将在实现指令时在后面的章节中介绍，所以现在让我们称之为 Function．\nNodeTransform 和 DirectiveTransform 实际上都是函数．你可以将它们视为转换 AST 的函数．\n请注意，NodeTransform 的结果是一个函数．在实现 transform 时，如果你实现它返回一个函数，该函数将在该节点的转换之后执行（它被称为 onExit 过程）．\n你想在节点的 transform 之后执行的任何处理都应该在这里描述．我将在稍后描述称为 traverseNode 的函数时解释这一点．\n接口的解释主要如上所述．\n\n作为更具体的实现，有用于转换元素的 transformElement 和用于转换表达式的 transformExpression 等．\n至于 DirectiveTransform 的实现，每个指令都有实现．\n这些实现在 compiler-core/src/transforms 中实现．具体的转换过程在这里实现．\n\nhttps://github.com/vuejs/core/tree/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/transforms\n\n图像 ↓\n\n![Transform type relationships](/figures/50-basic-template-compiler/transform/transform-type-relationships.svg)\n\n接下来，关于上下文，TransformContext 保存在这些转换期间使用的信息和函数．\n将来会添加更多，但现在这就足够了．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n}\n```\n\n## 转换器的实现\n\n现在让我们在实践中看看 transform 函数．首先，让我们从独立于每个转换过程内容的框架的一般解释开始．\n\n结构非常简单，只需生成上下文并使用 traverseNode 函数遍历节点．\n这个 traverseNode 函数是转换的主要实现．\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n}\n```\n\n在 traverseNode 中，基本上，它只是将保存在上下文中的 nodeTransforms（转换节点的函数集合）应用于节点．\n对于那些有子节点的，子节点也通过 traverseNode 传递．\n在接口解释期间提到的 onExit 的实现也在这里．\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  context.currentNode = node\n\n  const { nodeTransforms } = context\n  const exitFns = [] // 转换后要执行的操作\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context)\n\n    // 注册转换后要执行的操作\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit)\n      } else {\n        exitFns.push(onExit)\n      }\n    }\n    if (!context.currentNode) {\n      return\n    } else {\n      node = context.currentNode\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n\n  context.currentNode = node\n\n  // 执行转换后要执行的操作\n  let i = exitFns.length\n  while (i--) {\n    exitFns[i]() // 可以假设转换已完成而执行的操作\n  }\n}\n\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    traverseNode(child, context)\n  }\n}\n```\n\n接下来，让我们谈谈具体的转换过程．作为示例，让我们实现 transformElement．\n\n在 transformElement 中，我们主要将类型为 NodeTypes.ELEMENT 的节点转换为 VNodeCall．\n\n```ts\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n  isSelfClosing: boolean\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\n// ↓↓↓↓↓↓ 转换 ↓↓↓↓↓↓ //\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n```\n\n这是一个简单的对象到对象的转换，所以我认为不会很困难．让我们尝试通过阅读源代码来实现它．\n我将粘贴我这次假设的代码以防万一．（指令支持将在另一章中完成．）\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    const { tag, props } = node\n\n    const vnodeTag = `\"${tag}\"`\n    let vnodeProps: VNodeCall['props']\n    let vnodeChildren: VNodeCall['children']\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node)\n      vnodeProps = propsBuildResult.props\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0]\n        const type = child.type\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode\n        } else {\n          vnodeChildren = node.children\n        }\n      } else {\n        vnodeChildren = node.children\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren)\n  }\n}\n\nexport function buildProps(node: ElementNode): {\n  props: PropsExpression | undefined\n  directives: DirectiveNode[]\n} {\n  const { props } = node\n  let properties: ObjectExpression['properties'] = []\n  const runtimeDirectives: DirectiveNode[] = []\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i]\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop\n\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : '', true),\n        ),\n      )\n    } else {\n      // directives\n      // TODO:\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined\n  if (properties.length) {\n    propsExpression = createObjectExpression(properties)\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  }\n}\n```\n\n## 基于转换后的 AST 的代码生成\n\n由于我们为 Codegen 转换了 AST，我们也需要支持 Codegen．\n对于进入 Codegen 的 AST，假设 VNodeClass（以及它们拥有的节点）编写代码就足够了．\n期望的最终字符串表示与以前相同．\n\n现有的 Codegen 实现非常简单，所以让我们在这里使它更正式一些（因为它相当硬编码）．\n让我们也创建一个 Codegen 特定的上下文并将生成的代码推送到其中．\n此外，让我们在上下文中实现一些辅助函数（如缩进）．\n\n```ts\nexport interface CodegenContext {\n  source: string\n  code: string\n  indentLevel: number\n  line: 1\n  column: 1\n  offset: 0\n  push(code: string, node?: CodegenNode): void\n  indent(): void\n  deindent(withoutNewLine?: boolean): void\n  newline(): void\n}\n```\n\n我将在这里省略实现细节，但我只是为每个角色分离了函数，实现方法没有重大变化．\n由于我还没有能够支持指令，由于在该区域删除了临时实现，有些部分不工作，但\n如果代码大致按以下方式工作，就可以了！\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> Hello World! </p>\n      <p> Count: {{ count }} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/010_transformer)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/020-v-bind.md",
    "content": "# 让我们实现指令（v-bind）\n\n## 方法\n\n现在让我们实现指令，这是 Vue.js 的精髓．  \n像往常一样，我们将指令应用到转换器，出现在那里的接口称为 DirectiveTransform．  \nDirectiveTransform 接受 DirectiveNode 和 ElementNode 作为参数，并返回转换后的 Property．\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult\n\nexport interface DirectiveTransformResult {\n  props: Property[]\n}\n```\n\n首先，让我们检查这次我们要实现的开发者接口．\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const bind = { id: 'some-id', class: 'some-class', style: 'color: red' }\n    return { count: 1, bind }\n  },\n\n  template: `<div>\n  <p v-bind:id=\"count\"> v-bind:id=\"count\" </p>\n  <p :id=\"count * 2\"> :id=\"count * 2\" </p>\n\n  <p v-bind:[\"style\"]=\"bind.style\"> v-bind:[\"style\"]=\"bind.style\" </p>\n  <p :[\"style\"]=\"bind.style\"> :[\"style\"]=\"bind.style\" </p>\n\n  <p v-bind=\"bind\"> v-bind=\"bind\" </p>\n\n  <p :style=\"{ 'font-weight': 'bold' }\"> :style=\"{ font-weight: 'bold' }\" </p>\n  <p :style=\"'font-weight: bold;'\"> :style=\"'font-weight: bold;'\" </p>\n\n  <p :class=\"'my-class my-class2'\"> :class=\"'my-class my-class2'\" </p>\n  <p :class=\"['my-class']\"> :class=\"['my-class']\" </p>\n  <p :class=\"{ 'my-class': true }\"> :class=\"{ 'my-class': true }\" </p>\n  <p :class=\"{ 'my-class': false }\"> :class=\"{ 'my-class': false }\" </p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nv-bind 有各种表示法．详情请参考官方文档．  \n我们还将处理 class 和 style．\n\nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\n## AST 修改\n\n首先，让我们修改 AST．目前，exp 和 arg 都是简单的字符串，所以我们需要将它们更改为接受 ExpressionNode．\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined // 这里\n  arg: ExpressionNode | undefined // 这里\n}\n```\n\n让我再次解释 name，arg 和 exp．  \nname 是指令名称，如 v-bind 或 v-on．它可以是 on 或 bind．  \n由于我们这次实现 v-bind，它将是 bind．\n\narg 是由 : 指定的参数．对于 v-bind，它包括 id 和 style．  \n（在 v-on 的情况下，它包括 click 和 input．）\n\nexp 是右侧．在 v-bind:id=\"count\" 的情况下，包含 count．  \nexp 和 arg 都可以动态嵌入变量，所以它们的类型是 ExpressionNode．  \n（因为 arg 也可以像 v-bind:[key]=\"count\" 一样是动态的）\n\n![DirectiveNode shape for v-bind](/figures/50-basic-template-compiler/v-bind/directive-node-shape.svg)\n\n## 解析器修改\n\n我们将更新解析器实现以遵循这个 AST 修改．我们将 exp 和 arg 解析为 SimpleExpressionNode．\n\n我们还将解析 v-on 中使用的 @ 和插槽中使用的 #．  \n（由于考虑正则表达式很麻烦（而且在解释时逐渐添加它们很麻烦），我们现在将借用原始代码．）  \n参考：https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/parse.ts#L802\n\n由于代码有点长，我将在代码中写注释来解释．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  // .\n  // directive\n  const loc = getSelection(context, start)\n  // 这里的正则表达式是从原始源代码借用的\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match =\n      // 这里的正则表达式是从原始源代码借用的\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name,\n      )!\n\n    // 检查名称部分的匹配，如果以 \":\" 开头则将其视为 \"bind\"\n    let dirName =\n      match[1] ||\n      (startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : '')\n\n    let arg: ExpressionNode | undefined\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2])\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      )\n\n      let content = match[2]\n      let isStatic = true\n\n      // 如果是像 \"[arg]\" 这样的动态参数，将 isStatic 设置为 false 并提取内容作为内容\n      if (content.startsWith('[')) {\n        isStatic = false\n        if (!content.endsWith(']')) {\n          console.error(`Invalid dynamic argument expression: ${content}`)\n          content = content.slice(1)\n        } else {\n          content = content.slice(1, content.length - 1)\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      }\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    }\n  }\n}\n```\n\n通过这样，我们能够解析这次想要处理的 AST Node．\n\n## 转换器的实现\n\n接下来，让我们编写将此 AST 转换为 Codegen AST 的实现．  \n由于它有点复杂，我在下图中总结了流程．请先看一下．  \n一般来说，必要的项目是 v-bind 是否有参数，是否是 class 或 style．  \n※ 省略了这次不涉及的处理部分．（请注意这个图不是很严格．）\n\n![v-bind transform flow](/figures/50-basic-template-compiler/v-bind/transform-vbind-flow.svg)\n\n首先，作为前提，由于指令基本上是为元素声明的，  \n与指令相关的转换器从 transformElement 调用．\n\n由于我们这次想要实现 v-bind，我们将实现一个名为 transformVBind 的函数，  \n但需要注意的一点是，这个函数只转换具有 args 的声明．\n\ntransformVBind 的作用是将\n\n```\nv-bind:id=\"count\"\n```\n\n转换为像这样的对象（实际上是表示此对象的 Codegen Node）\n\n```ts\n{\n  id: count\n}\n```\n\n在原始实现中也给出了以下解释．\n\n> codegen for the entire props object. This transform here is only for v-bind _with_ args.\n\n引用自：https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/transforms/vBind.ts#L13C1-L14C16\n\n正如你从流程中可以看到的，transformElement 检查指令的 arg，如果它不存在，它不执行 transformVBind，而是将其转换为对 mergeProps 的函数调用．\n\n```vue\n<p v-bind=\"bindingObject\" class=\"my-class\">hello</p>\n```\n\n↓\n\n```ts\nh('p', mergeProps(bindingObject, { class: 'my-class' }), 'hello')\n```\n\n另外，对于 class 和 style，它们有各种开发者接口，所以需要进行规范化．  \nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\n实现名为 normalizeClass 和 normalizeStyle 的函数，并分别应用它们．\n\n如果 arg 是动态的，无法确定具体的，所以实现一个名为 normalizeProps 的函数并调用它．（它在内部调用 normalizeClass 和 normalizeStyle）\n\n现在我们已经实现到这里，让我们看看它是如何工作的！\n\n![v-bind test result in the browser](/figures/50-basic-template-compiler/v-bind/vbind-test-result.png)\n\n看起来很棒！\n\n下次，我们将实现 v-on．\n\n到此为止的源代码：  \n[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/020_v_bind)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/022-transform-expression.md",
    "content": "# transformExpression\n\n## 要实现的开发者接口和当前挑战\n\n首先，看看这个组件．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button :onClick=\"increment\">count + count is: {{ count + count }}</button>\n  </div>\n</template>\n```\n\n这个组件有几个问题．  \n由于这个组件是用 SFC 编写的，没有使用 `with` 语句．  \n换句话说，绑定没有正常工作．\n\n让我们看看编译后的代码．\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: increment }), [\n      'count + count is: ',\n      _ctx.count + count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n- 问题 1：注册为事件处理器的 `increment` 无法访问 `_ctx`．  \n  这是因为在之前的 `v-bind` 实现中没有添加前缀．\n- 问题 2：表达式 `count + count` 无法访问 `_ctx`．  \n  关于 mustache 语法，它只在开头添加 `_ctx.`，无法处理其他标识符．  \n  因此，表达式中出现的所有标识符都需要加上 `_ctx.` 前缀．这适用于所有部分，不仅仅是 mustache．\n\n看起来需要一个过程来为表达式中出现的标识符添加 `_ctx.`．\n\n::: details 期望的编译结果\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: _ctx.increment }), [\n      'count + count is: ',\n      _ctx.count + _ctx.count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n:::\n\n::: warning\n\n实际上，原始实现采用了稍微不同的方法．\n\n如下所示，在原始实现中，从 `setup` 函数绑定的任何内容都通过 `$setup` 解析．\n\n![Original resolve bindings output](/figures/50-basic-template-compiler/transform-expression/resolve-bindings-original.png)\n\n然而，实现这个有点困难，所以我们将简化它并通过添加 `_ctx.` 来实现．（所有 props 和 setup 都将从 `_ctx` 解析）\n\n:::\n\n## 实现方法\n\n简单来说，我们想要做的是\"在 ExpressionNode 上的每个标识符（名称）的开头添加 `_ctx.`\"．\n\n让我更详细地解释一下．  \n作为回顾，程序通过解析被表示为 AST．  \n表示程序的 AST 主要有两种类型的节点：Expression 和 Statement．  \n这些通常被称为表达式和语句．\n\n```ts\n1 // 这是一个 Expression\nident // 这是一个 Expression\nfunc() // 这是一个 Expression\nident + func() // 这是一个 Expression\n\nlet a // 这是一个 Statement\nif (!a) a = 1 // 这是一个 Statement\nfor (let i = 0; i < 10; i++) a++ // 这是一个 Statement\n```\n\n我们这里要考虑的是 Expression．  \n有各种类型的表达式．Identifier 是其中之一，它是由标识符表示的表达式．  \n（你可以将其视为一般的变量名）\n\nIdentifier 出现在表达式的各个地方．\n\n```ts\n1 // 无\nident // ident --- (1)\nfunc() // func --- (2)\nident + func() // ident, func --- (3)\n```\n\n这样，Identifier 出现在表达式的各个地方．\n\n你可以通过在以下网站输入程序来观察 ExpressionNode 上的各种 Identifier，该网站允许你观察 AST．  \nhttps://astexplorer.net/#/gist/670a1bee71dbd50bec4e6cc176614ef8/9a9ff250b18ccd9000ed253b0b6970696607b774\n\n## 搜索标识符\n\n现在我们知道了我们想要做什么，我们如何实现它？\n\n看起来很困难，但实际上很简单．我们将使用一个名为 estree-walker 的库．  \nhttps://github.com/Rich-Harris/estree-walker\n\n我们将使用这个库来遍历通过 babel 解析获得的 AST．  \n用法非常简单．只需将 AST 传递给 `walk` 函数，并将每个 Node 的处理描述为第二个参数．  \n这个 `walk` 函数逐个节点遍历 AST，到达该 Node 时的处理通过 `enter` 选项完成．  \n除了 `enter`，还有像 `leave` 这样的选项来在该 Node 结束时处理．我们这次只使用 `enter`．\n\n创建一个名为 `compiler-core/babelUtils.ts` 的新文件，并实现可以对 Identifier 执行操作的实用函数．\n\n首先，安装 estree-walker．\n\n```sh\nnpm install estree-walker\n\nnpm install -D @babel/types # 也安装这个\n```\n\n```ts\nimport { Identifier, Node } from '@babel/types'\n\nimport { walk } from 'estree-walker'\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        onIdentifier(node)\n      }\n    },\n  })\n}\n```\n\n然后，为表达式生成 AST 并将其传递给此函数，在重写节点的同时执行转换．\n\n## transformExpression 的实现\n\n### InterpolationNode 的 AST 和解析器更改\n\n我们将实现转换过程的主体 transformExpression．\n\n首先，我们将修改 InterpolationNode，使其具有 SimpleExpressionNode 而不是字符串作为其内容．\n\n```ts\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // [!code --]\n  content: ExpressionNode // [!code ++]\n}\n```\n\n通过这个更改，我们还需要修改 parseInterpolation．\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  // .\n  // .\n  // .\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  }\n}\n```\n\n### 转换器的实现（主体）\n\n为了使表达式转换在其他转换器中可用，我们将其提取为名为 `processExpression` 的函数．\n在 transformExpression 中，我们将处理 INTERPOLATION 和 DIRECTIVE 的 ExpressionNode．\n\n```ts\nexport const transformExpression: NodeTransform = node => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp\n        const arg = dir.arg\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          dir.exp = processExpression(exp)\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg)\n        }\n      }\n    }\n  }\n}\n\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // TODO:\n}\n```\n\n接下来，让我们解释 processExpression 的实现．\n首先，我们将实现一个名为 rewriteIdentifier 的函数来重写 node 内的 Identifier．\n如果 node 是单个 Identifier，我们简单地应用此函数并返回它．\n\n需要注意的一点是，这个 processExpression 特定于 SFC（单文件组件）情况（不使用 with 语句的情况）．\n换句话说，如果设置了 isBrowser 标志，我们实现它简单地返回 node．\n我们修改实现以通过 ctx 接收标志．\n\n另外，我想保留像 true 和 false 这样的字面量，所以我将为字面量创建一个白名单．\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    // 对浏览器不做任何处理\n    return node\n  }\n\n  const rawExp = node.content\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`\n  }\n\n  if (isSimpleIdentifier(rawExp)) {\n    node.content = rewriteIdentifier(rawExp)\n    return node\n  }\n\n  // TODO:\n}\n```\n\n`makeMap` 是在 vuejs/core 中实现的用于存在性检查的辅助函数，它返回一个布尔值，指示是否与用逗号分隔定义的字符串匹配．\n\n```ts\nexport function makeMap(\n  str: string,\n  expectsLowerCase?: boolean,\n): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null)\n  const list: Array<string> = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]\n}\n```\n\n问题在于下一步，即如何转换 SimpleExpressionNode（不是简单的 Identifier）并转换节点．\n在以下讨论中，请注意我们将处理两个不同的 AST：Babel 生成的 JavaScript AST 和 chibivue 定义的 AST．\n为了避免混淆，我们在本章中将前者称为 estree，后者称为 AST．\n\n策略分为两个阶段．\n\n1. 在收集节点的同时替换 estree 节点\n2. 基于收集的节点构建 AST\n\n首先，让我们从阶段 1 开始．\n这相对简单．如果我们可以用 Babel 解析原始 SimpleExpressionNode 内容（字符串）并获得 estree，我们可以通过我们之前创建的实用函数传递它并应用 rewriteIdentifier．\n此时，我们收集 estree 节点．\n\n```ts\nimport { parse } from '@babel/parser'\nimport { Identifier } from '@babel/types'\nimport { walkIdentifiers } from '../babelUtils'\n\ninterface PrefixMeta {\n  start: number\n  end: number\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  // .\n  // .\n  // .\n  const ast = parse(`(${rawExp})`).program // ※ 这个 ast 指的是 estree。\n  type QualifiedId = Identifier & PrefixMeta\n  const ids: QualifiedId[] = []\n\n  walkIdentifiers(ast, node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  })\n\n  // TODO:\n}\n```\n\n需要注意的一点是，到目前为止，我们只操作了 estree，没有操作 ast 节点．\n\n### CompoundExpression\n\n接下来，让我们进入阶段 2．在这里，我们将定义一个名为 `CompoundExpressionNode` 的新 AST Node．\nCompound 意味着\"组合\"或\"复杂性\"．这个 Node 有 children，它们采用稍微特殊的值．\n首先，让我们看看 AST 的定义．\n\n```ts\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n}\n```\n\nChildren 采用如上所示的数组．\n要理解这个 Node 中的 children 代表什么，看具体例子会更容易，所以让我们给出一些例子．\n\n以下表达式将被解析为以下 CompoundExpressionNode：\n\n```ts\ncount * 2\n```\n\n```json\n{\n  \"type\": 7,\n  \"children\": [\n    {\n      \"type\": 4,\n      \"isStatic\": false,\n      \"content\": \"_ctx.count\"\n    },\n    \" * 2\"\n  ]\n}\n```\n\n这是一种相当奇怪的感觉．\"children\" 采用字符串类型的原因是因为它采用这种形式．\n在 CompoundExpression 中，Vue 编译器将其分为必要的粒度，并部分表示为字符串或部分表示为 Node．\n具体来说，在像这样重写 Expression 中存在的 Identifier 的情况下，只有 Identifier 部分被分为另一个 SimpleExpressionNode．\n\n换句话说，我们要做的是基于收集的 estree 的 Identifier Node 和源生成这个 CompoundExpression．\n以下代码是为此的实现．\n\n```ts\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // .\n  // .\n  // .\n  const children: CompoundExpressionNode['children'] = []\n  ids.sort((a, b) => a.start - b.start)\n  ids.forEach((id, i) => {\n    const start = id.start - 1\n    const end = id.end - 1\n    const last = ids[i - 1]\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start)\n    if (leadingText.length) {\n      children.push(leadingText)\n    }\n\n    const source = rawExp.slice(start, end)\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    )\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end))\n    }\n  })\n\n  let ret\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc)\n  } else {\n    ret = node\n  }\n\n  return ret\n}\n```\n\nBabel 解析的 Node 有 start 和 end（它对应于原始字符串的位置信息），所以我们基于此从 rawExp 中提取相应的部分并仔细分割．\n请仔细查看源代码了解更多详细信息．如果你理解到目前为止的策略，你应该能够阅读它．（另外，请查看 advancePositionWithClone 等的实现，因为它们是新实现的．）\n\n现在我们可以生成 CompoundExpressionNode，让我们也在 Codegen 中支持它．\n\n```ts\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNode(node.content, context, option)\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i]\n    if (isString(child)) {\n      // 如果是字符串，按原样推送\n      context.push(child)\n    } else {\n      // 对于其他任何内容，为 Node 生成 codegen\n      genNode(child, context, option)\n    }\n  }\n}\n```\n\n（genInterpolation 已经变成了只是 genNode，但我现在将保留它．）\n\n## 试试看\n\n现在我们已经实现到这里，让我们完成编译器并尝试运行它！\n\n```ts\n// 添加 transformExpression\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformElement], { bind: transformBind }] // [!code --]\n  return [[transformExpression, transformElement], { bind: transformBind }] // [!code ++]\n}\n```\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(3)\n    const getMsg = (count: number) => `Count: ${count}`\n    return { count, getMsg }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> {{ 'Message is \"' + getMsg(count) + '\"'}} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/022_transform_expression)\n```\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/025-v-on.md",
    "content": "# v-on 的支持\n\n## 重构\n\n在继续实现之前，让我们进行一些重构．  \n目前，在 codegen 生成的代码中，我们从 `shared` 和 `runtime-core` 导入（或解构）了许多辅助函数．  \n而且在 codegen（和 transform）的实现中，我们硬编码了函数名称．这不是很明智．\n\n这次，让我们将它们重构为 `runtime-helper` 并用符号集中管理，进一步地，更改实现以仅导入必要的内容．\n\n首先，让我们在 `compiler-core/runtimeHelpers.ts` 中实现表示每个辅助函数的符号．  \n到目前为止，我们一直使用 `h` 函数来生成 VNode，但这次，让我们按照原始实现更改为使用 `createVNode`．  \n从 `runtime-core/vnode` 导出 `createVNode`，并在 `genVNodeCall` 中，更改代码以调用 `createVNode` 而不是 `genVNodeCall`．\n\n```ts\nexport const CREATE_VNODE = Symbol()\nexport const MERGE_PROPS = Symbol()\nexport const NORMALIZE_CLASS = Symbol()\nexport const NORMALIZE_STYLE = Symbol()\nexport const NORMALIZE_PROPS = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: 'createVNode',\n  [MERGE_PROPS]: 'mergeProps',\n  [NORMALIZE_CLASS]: 'normalizeClass',\n  [NORMALIZE_STYLE]: 'normalizeStyle',\n  [NORMALIZE_PROPS]: 'normalizeProps',\n}\n```\n\n使符号在 `CallExpression` 中可用作 `callee`．\n\n```ts\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION\n  callee: string | symbol\n}\n```\n\n在 `TransformContext` 中实现一个区域来注册辅助函数和注册它们的函数．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n  helpers: Map<symbol, number> // 这个\n  helper<T extends symbol>(name: T): T // 这个\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n    helpers: new Map(),\n    helper(name) {\n      const count = context.helpers.get(name) || 0\n      context.helpers.set(name, count + 1)\n      return name\n    },\n  }\n\n  return context\n}\n```\n\n用这个辅助函数替换硬编码的部分，并修改 Preamble 以使用注册的辅助函数．\n\n```ts\n// 示例)\npropsExpression = createCallExpression('mergeProps', mergeArgs, elementLoc)\n// ↓\npropsExpression = createCallExpression(\n  context.helper(MERGE_PROPS),\n  mergeArgs,\n  elementLoc,\n)\n```\n\n将 `context` 传递给 `createVNodeCall` 并在其中注册 `CREATE_VNODE`．\n\n```ts\nexport function createVNodeCall(\n  context: TransformContext | null, // 这个\n  tag: VNodeCall['tag'],\n  props?: VNodeCall['props'],\n  children?: VNodeCall['children'],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  // 这里 ------------------------\n  if (context) {\n    context.helper(CREATE_VNODE)\n  }\n  // ------------------------\n\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  }\n}\n```\n\n```ts\nfunction genVNodeCall(\n  node: VNodeCall,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n  const { tag, props, children } = node\n\n  push(helper(CREATE_VNODE) + `(`, node) // 调用 createVNode\n  genNodeList(genNullableArgs([tag, props, children]), context, option)\n  push(`)`)\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  root.helpers = new Set([...context.helpers.keys()]) // 将辅助函数添加到 root\n}\n```\n\n```ts\n// 根据原始实现添加 `_` 作为前缀来为其设置别名\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context\n\n  // 基于在 ast 中注册的辅助函数生成辅助函数声明\n  const helpers = Array.from(ast.helpers)\n  push(\n    `const { ${helpers.map(aliasHelper).join(', ')} } = ${runtimeGlobalName}\\n`,\n  )\n  newline()\n}\n```\n\n```ts\n// 在 genCallExpression 中处理符号并将它们转换为辅助函数调用。\n\nexport interface CodegenContext {\n  // .\n  // .\n  // .\n  helper(key: symbol): string\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    // .\n    // .\n    // .\n    helper(key) {\n      return `_${helperNameMap[key]}`\n    },\n  }\n  // .\n  // .\n  // .\n  return context\n}\n\n// .\n// .\n// .\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n\n  // 如果是符号，从辅助函数中获取它\n  const callee = isString(node.callee) ? node.callee : helper(node.callee)\n\n  push(callee + `(`, node)\n  genNodeList(node.arguments, context, option)\n  push(`)`)\n}\n```\n\n通过这样，我们这次进行的重构就完成了．我们能够清理硬编码的部分！\n\n::: details 编译结果\n\n※ 注意\n\n- 输入使用的是前一个游乐场的输入\n- 实际上在 `function` 前面有一个 `return`\n- 生成的代码用 prettier 格式化\n\n当你这样看时，有太多不必要的换行和空格...\n\n好吧，让我们在其他地方改进这个．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      normalizeClass: _normalizeClass,\n    } = ChibiVue\n\n    return _createVNode('div', null, [\n      '\\n  ',\n      _createVNode('p', _normalizeProps({ id: count }), ' v-bind:id=\"count\" '),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ id: count * 2 }),\n        ' :id=\"count * 2\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' v-bind:[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' :[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode('p', _normalizeProps(bind), ' v-bind=\"bind\" '),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: { 'font-weight': 'bold' } }),\n        ' :style=\"{ font-weight: \\'bold\\' }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: 'font-weight: bold;' }),\n        ' :style=\"\\'font-weight: bold;\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass('my-class my-class2'),\n        }),\n        ' :class=\"\\'my-class my-class2\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ class: _normalizeClass(['my-class']) }),\n        ' :class=\"[\\'my-class\\']\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': true }),\n        }),\n        ' :class=\"{ \\'my-class\\': true }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': false }),\n        }),\n        ' :class=\"{ \\'my-class\\': false }\" ',\n      ),\n      '\\n',\n    ])\n  }\n}\n```\n\n:::\n\n## v-on\n\n## 这次要实现的开发者接口\n\n现在让我们继续实现 v-on．\n\nv-on 也有各种开发者接口．\nhttps://vuejs.org/guide/essentials/event-handling.html\n\n这是我们这次的目标．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    const increment = (e: Event) => {\n      console.log(e)\n      count.value++\n    }\n    return { count, increment, state: { increment }, eventName: 'click' }\n  },\n\n  template: `<div>\n    <p>count: {{ count }}</p>\n\n    <button v-on:click=\"increment\">v-on:click=\"increment\"</button>\n    <button v-on:[eventName]=\"increment\">v-on:click=\"increment\"</button>\n    <button @click=\"increment\">@click=\"increment\"</button>\n    <button v-on=\"{ click: increment }\">v-on=\"{ click: increment }\"</button>\n\n    <button @click=\"state.increment\">v-on:click=\"increment\"</button>\n    <button @click=\"count++\">@click=\"count++\"</button>\n    <button @click=\"() => count++\">@click=\"() => count++\"</button>\n    <button @click=\"increment($event)\">@click=\"increment($event)\"</button>\n    <button @click=\"e => increment(e)\">@click=\"e => increment(e)\"</button>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n## 我想要做的事情\n\n实际上，关于解析器的实现，前一章的实现就足够了，问题在于转换器的实现．\n转换的内容主要根据 arg 的存在与否和 exp 的类型而变化．\n当没有 arg 时，需要做的事情几乎与 v-bind 相同．\n\n换句话说，需要考虑的是可以作为 arg 的 exp 类型以及为它们转换必要的 AST Node．\n\n- 任务 1\n  分配一个函数．\n  这是最简单的情况．\n\n  ```html\n  <button v-on:click=\"increment\">increment</button>\n  ```\n\n- 任务 2\n  在现场编写函数表达式．\n  在这种情况下，你可以接收事件作为第一个参数．\n\n  ```html\n  <button v-on:click=\"(e) => increment(e)\">increment</button>\n  ```\n\n- 任务 3\n  编写除函数以外的语句．\n\n  ```html\n  <button @click=\"count = 0\">reset</button>\n  ```\n\n  看起来这个表达式需要转换为以下函数．\n\n  ```ts\n  ;() => {\n    count = 0\n  }\n  ```\n\n- 任务 4\n  在像任务 3 这样的情况下，你可以使用标识符 `$event`．\n  这是处理事件对象的情况．\n\n  ```ts\n  const App = defineComponent({\n    setup() {\n      const count = ref(0)\n      const increment = (e: Event) => {\n        console.log(e)\n        count.value++\n      }\n      return { count, increment, object }\n    },\n\n    template: `\n      <div class=\"container\">\n        <button @click=\"increment($event)\">increment($event)</button>\n        <p> {{ count }} </p>\n      </div>\n      `,\n  })\n  // 不能像 @click=\"() => increment($event)\" 这样使用。\n  ```\n\n  看起来它需要转换为以下函数．\n\n  ```ts\n  $event => {\n    increment($event)\n  }\n  ```\n\n## 实现\n\n### 当没有 arg 时\n\n暂时，让我们实现没有 arg 的情况，因为它与 v-bind 相同．\n这是我在前一章中留下 TODO 注释的部分．它在 transformElement 附近．\n\n```ts\nconst isVBind = name === 'bind'\nconst isVOn = name === 'on' // --------------- 这里\n\n// v-bind 和 v-on 没有参数的特殊情况\nif (!arg && (isVBind || isVOn)) {\n  if (exp) {\n    if (isVBind) {\n      pushMergeArg()\n      mergeArgs.push(exp)\n    } else {\n      // -------------------------------------- 这里\n      // v-on=\"obj\" -> toHandlers(obj)\n      pushMergeArg({\n        type: NodeTypes.JS_CALL_EXPRESSION,\n        loc,\n        callee: context.helper(TO_HANDLERS),\n        arguments: [exp],\n      })\n    }\n  }\n  continue\n}\n\nconst directiveTransform = context.directiveTransforms[name]\nif (directiveTransform) {\n  const { props } = directiveTransform(prop, node, context)\n  if (isVOn && arg && !isStaticExp(arg)) {\n    pushMergeArg(createObjectExpression(props, elementLoc))\n  } else {\n    properties.push(...props)\n  }\n} else {\n  // TODO: 自定义指令。\n}\n```\n\n我将这次实现名为 `TO_HANDLERS` 的辅助函数．\n\n这个函数将以 `v-on=\"{ click: increment }\"` 形式传递的对象转换为 `{ onClick: increment }` 的形式．\n没有什么特别困难的．\n\n```ts\nimport { toHandlerKey } from '../../shared'\n\n/**\n * 用于在 v-on=\"obj\" 中为键添加 \"on\" 前缀\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {}\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key]\n  }\n  return ret\n}\n```\n\n这完成了没有 arg 时的实现．\n让我们继续实现有 arg 时的情况．\n\n### transformVOn\n\n现在，让我们继续这次的主题，即 v-on．v-on 的 exp 有各种格式．\n\n```ts\nincrement\n\nstate.increment\n\ncount++\n\n;() => count++\n\nincrement($event)\n\ne => increment(e)\n```\n\n首先，这些格式可以大致分为两类：\"函数\"和\"语句\"．在 Vue 中，如果是单个 Identifier，单个 MemberExpression 或函数表达式，则将其视为函数．否则，它是一个语句．在源代码中，它似乎被称为 inlineStatement．\n\n```ts\n// 函数（※ 为了方便，请将这些视为函数表达式。）\nincrement\nstate.increment\n;() => count++\ne => increment(e)\n\n// inlineStatement\ncount++\nincrement($event)\n```\n\n换句话说，这次的实现流程如下：\n\n1. 首先，确定它是否是函数（单个 Identifier 或单个 MemberExpression 或函数表达式）．\n\n2-1. 如果是函数，生成 `eventName: exp` 形式的 ObjectProperty，不进行任何转换．\n\n2-2. 如果不是函数（如果是 inlineStatement），将其转换为 `$event => { ${exp} }` 的形式并生成 ObjectProperty．\n\n这就是基本思路．\n\n#### 确定是函数表达式还是语句\n\n让我们从实现确定开始．是否是函数表达式是使用正则表达式完成的．\n\n```ts\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/\n\nconst isFn = fnExpRE.test(exp.content)\n```\n\n是否是单个 Identifier 或单个 MemberExpression 是用名为 `isMemberExpression` 的函数实现的．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\n```\n\n这个 `isMemberExpression` 函数相当复杂，实现很长．有点长，所以我在这里省略它．（如果你感兴趣，请查看代码．）\n\n一旦我们确定了这一点，它是 inlineStatement 的条件就是除了这些之外的任何东西．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isFnExp = fnExpRE.test(exp.content)\nconst isInlineStatement = !(isMemberExp || isFnExp)\n```\n\n现在我们已经确定了这一点，让我们基于这个结果实现转换过程．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))\nconst hasMultipleStatements = exp.content.includes(`;`)\n\nif (isInlineStatement) {\n  // 将内联语句包装在函数表达式中\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\n### 问题\n\n实际上，上述实现有一个小问题．\n\n问题在于 `$event`，因为在 `dir.exp` 中，我们需要使用 `processExpression` 处理从 setup 绑定的值，但问题在于 `$event`．\n在 AST 上，`$event` 也被视为 Identifier，所以如果我们保持原样，它将被加上 `_ctx.` 前缀．\n\n所以让我们做一点改进．让我们在 `transformContext` 中注册一个局部变量．在 `walkIdentifiers` 中，如果有局部变量，我们不会执行 `onIdentifier`．\n\n```ts\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  identifiers: Object.create(null),\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      addId(exp)\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      removeId(exp)\n    }\n  },\n}\n\nfunction addId(id: string) {\n  const { identifiers } = context\n  if (identifiers[id] === undefined) {\n    identifiers[id] = 0\n  }\n  identifiers[id]!++\n}\n\nfunction removeId(id: string) {\n  context.identifiers[id]!--\n}\n```\n\n```ts\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null), // [!code ++]\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        const isLocal = !!knownIds[node.name] // [!code ++]\n        // prettier-ignore\n        if (!isLocal) { // [!code ++]\n          onIdentifier(node);\n        } // [!code ++]\n      }\n    },\n  })\n}\n```\n\n然后，当在 `processExpression` 中使用 `walkIdentifiers` 时，我们将从 `context` 中拉取 `identifiers`．\n\n```ts\nconst ids: QualifiedId[] = []\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers) // [!code ++]\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // [!code ++]\n)\n```\n\n最后，当在 `transformOn` 中转换时，让我们注册 `$event`．\n\n```ts\n// prettier-ignore\nif (!context.isBrowser) { // [!code ++]\n  isInlineStatement && context.addIdentifiers(`$event`); // [!code ++]\n  exp = dir.exp = processExpression(exp, context); // [!code ++]\n  isInlineStatement && context.removeIdentifiers(`$event`); // [!code ++]\n} // [!code ++]\n\nif (isInlineStatement) {\n  // 将内联语句包装在函数表达式中\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\n由于 v-on 需要一些特殊处理，并且由于它在 `transformOn` 中单独处理，我们将在 `transformExpression` 中跳过它．\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  // .\n  // .\n  // .\n  if (\n    exp &&\n    exp.type === NodeTypes.SIMPLE_EXPRESSION &&\n    !(dir.name === 'on' && arg) // [!code ++]\n  ) {\n    dir.exp = processExpression(exp, ctx)\n  }\n}\n```\n\n现在，我们已经完成了这次的关键部分．让我们实现剩余的必要部分并完成 v-on！！\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/025_v_on)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/027-event-modifier.md",
    "content": "# 事件修饰符\n\n## 这次要做的事情\n\n由于我们上次实现了 v-on 指令，现在让我们实现事件修饰符．\n\nVue.js 有对应于 preventDefault 和 stopPropagation 的修饰符．\n\nhttps://vuejs.org/guide/essentials/event-handling.html\n\n这次，让我们以以下开发者接口为目标．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const inputText = ref('')\n\n    const buffer = ref('')\n    const handleInput = (e: Event) => {\n      const target = e.target as HTMLInputElement\n      buffer.value = target.value\n    }\n    const submit = () => {\n      inputText.value = buffer.value\n      buffer.value = ''\n    }\n\n    return { inputText, buffer, handleInput, submit }\n  },\n\n  template: `<div>\n    <form @submit.prevent=\"submit\">\n      <label>\n        Input Data\n        <input :value=\"buffer\" @input=\"handleInput\" />\n      </label>\n      <button>submit</button>\n    </form>\n    <p>inputText: {{ inputText }}</p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n特别是，请注意以下部分．\n\n```html\n<form @submit.prevent=\"submit\"></form>\n```\n\n有一个 `@submit.prevent` 的描述．这意味着在调用 submit 事件处理器时，执行 `preventDefault`．\n\n如果不包含 `.prevent`，提交时页面将重新加载．\n\n## AST 和解析器的实现\n\n由于我们要向模板添加新语法，需要对解析器和 AST 进行更改．\n\n首先，让我们看看 AST．这很简单，只需向 `DirectiveNode` 添加一个名为 `modifiers`（字符串数组）的属性．\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined\n  arg: ExpressionNode | undefined\n  modifiers: string[] // 添加这个\n}\n```\n\n让我们相应地实现解析器．\n\n实际上，这很容易，因为它已经包含在从原始源代码借用的正则表达式中．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  const modifiers = match[3] ? match[3].slice(1).split('.') : [] // 从匹配结果中提取修饰符\n  return {\n    type: NodeTypes.DIRECTIVE,\n    name: dirName,\n    exp: value && {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      content: value.content,\n      isStatic: false,\n      loc: value.loc,\n    },\n    loc,\n    arg,\n    modifiers, // 包含在返回中\n  }\n}\n```\n\n是的．通过这样，AST 和解析器的实现就完成了．\n\n## compiler-dom/transform\n\n让我们稍微回顾一下当前的编译器架构．\n\n当前的配置如下．\n\n![Compiler architecture before DOM modifiers](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-core-only.svg)\n\n当你再次理解 compiler-core 和 compiler-dom 的角色时，  \ncompiler-core 提供不依赖于 DOM 的编译器功能，如生成和转换 AST．\n\n到目前为止，我们在 compiler-core 中实现了 v-on 指令，但这只是将符号 `@click=\"handle\"` 转换为对象 `{ onClick: handle }`，  \n它不执行任何依赖于 DOM 的处理．\n\n现在，让我们看看这次我们想要实现的内容．  \n这次，我们想要生成实际执行 `e.preventDefault()` 或 `e.stopPropagation()` 的代码．  \n这些严重依赖于 DOM．\n\n因此，我们也将在 compiler-dom 端实现转换器．我们将在这里实现与 DOM 相关的转换器．\n\n在 compiler-core 中，我们需要考虑 compiler-core 中的 transform 和在 compiler-dom 中实现的 transform 之间的交互．  \n交互是如何在执行 compiler-core 中的 transform 的同时实现在 compiler-dom 中实现的 transform．\n\n所以首先，让我们修改在 compiler-core 中实现的 `DirectiveTransform` 接口．\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult, // 添加\n) => DirectiveTransformResult\n```\n\n我添加了 `augmentor`．  \n嗯，这只是一个回调函数．通过允许接收回调作为 `DirectiveTransform` 接口的一部分，我们使转换函数可扩展．\n\n在 compiler-dom 中，我们将实现一个包装在 compiler-core 中实现的转换器的转换器．\n\n```ts\n// 实现示例\n\n// compiler-dom 端的实现\n\nimport { transformOn as baseTransformOn } from 'compiler-core'\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransformOn(dir, node, context, () => {\n    /** 在这里实现 compiler-dom 自己的实现 */\n    return {\n      /** */\n    }\n  })\n}\n```\n\n如果你将在 compiler-dom 端实现的这个 `transformOn` 作为选项传递给编译器，就可以了．  \n这是关系的图表．  \n不是从 compiler-dom 传递所有转换器，而是在 compiler-core 中实现默认实现，配置允许添加额外的转换器．\n\n![Compiler architecture with DOM augmentor](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-dom-augmentor.svg)\n\n通过这样，compiler-core 可以执行不依赖于 DOM 的转换器，compiler-dom 可以在执行 compiler-core 中的转换器的同时实现依赖于 DOM 的处理．\n\n## 转换器的实现\n\n现在，让我们在 compiler-dom 端实现转换器．\n\n我们应该如何转换它？现在，由于即使我们简单地说\"修饰符\"也有各种类型的修饰符，让我们对它们进行分类，以便我们可以考虑未来的可能性．\n\n这次，我们将实现\"事件修饰符\"．让我们首先将其提取为 `eventModifiers`．\n\n```ts\nconst isEventModifier = makeMap(\n  // 事件传播管理\n  `stop,prevent,self`,\n)\n\nconst resolveModifiers = (modifiers: string[]) => {\n  const eventModifiers = []\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i]\n    if (isEventModifier(modifier)) {\n      eventModifiers.push(modifier)\n    }\n  }\n\n  return { eventModifiers }\n}\n```\n\n现在我们已经提取了 `eventModifiers`，我们应该如何使用它？总之，我们将在 runtime-dom 端实现一个名为 `withModifiers` 的辅助函数，并将其转换为调用该函数的表达式．\n\n```ts\n// runtime-dom/runtimeHelpers.ts\n\nexport const V_ON_WITH_MODIFIERS = Symbol()\n```\n\n```ts\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, baseResult => {\n    const { modifiers } = dir\n    if (!modifiers.length) return baseResult\n\n    let { key, value: handlerExp } = baseResult.props[0]\n    const { eventModifiers } = resolveModifiers(modifiers)\n\n    if (eventModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(eventModifiers),\n      ])\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    }\n  })\n}\n```\n\n通过这样，转换器的实现几乎完成了．\n\n现在让我们在 compiler-dom 端实现 `withModifiers`．\n\n## `withModifiers` 的实现\n\n让我们在 runtime-dom/directives/vOn.ts 中继续实现．\n\n实现非常简单．\n\n为事件修饰符实现一个保护函数，并实现它，使其运行与数组中接收的修饰符数量一样多的次数．\n\n```ts\nconst modifierGuards: Record<string, (e: Event) => void | boolean> = {\n  stop: e => e.stopPropagation(),\n  prevent: e => e.preventDefault(),\n  self: e => e.target !== e.currentTarget,\n}\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]]\n      if (guard && guard(event)) return\n    }\n    return fn(event, ...args)\n  }\n}\n```\n\n这就是实现的结束．\n\n让我们检查操作！如果按下按钮时输入内容反映在屏幕上而页面没有重新加载，就可以了！\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier)\n\n## 其他修饰符\n\n现在我们已经走到这一步，让我们实现其他修饰符．\n\n基本的实现方法是相同的．\n\n让我们按如下方式对修饰符进行分类：\n\n```ts\nconst keyModifiers = []\nconst nonKeyModifiers = []\nconst eventOptionModifiers = []\n```\n\n然后，生成必要的映射并用 `resolveModifiers` 对它们进行分类．\n\n需要注意的两点是：\n\n- 修饰符名称和实际 DOM API 名称之间的差异\n- 实现一个新的辅助函数来执行特定的键事件（withKeys）\n\n请在阅读实际代码的同时尝试实现！\n如果你已经走到这一步，你应该能够做到．\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier2)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/030-fragment.md",
    "content": "# 实现 Fragment\n\n## 当前实现的问题\n\n让我们尝试在游乐场中运行以下代码：\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n你可能会遇到这样的错误：\n\n![Fragment error result in the browser](/figures/50-basic-template-compiler/fragment/fragment-error-result.png)\n\n查看错误消息，似乎与 Function 构造函数有关．\n\n换句话说，代码生成似乎在某种程度上是成功的，所以让我们看看实际生成了什么代码．\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode } = ChibiVue\n\n    return _createVNode(\"header\", null, \"header\")\"\\n  \"_createVNode(\"main\", null, \"main\")\"\\n  \"_createVNode(\"footer\", null, \"footer\")\n   }\n}\n```\n\n`return` 语句后的代码是不正确的．当前的代码生成实现不处理根是数组（即不是单个节点）的情况．\n\n我们将修复这个问题．\n\n## 应该生成什么代码？\n\n即使我们正在进行修改，应该生成什么样的代码？\n\n总之，代码应该看起来像这样：\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode, Fragment: _Fragment } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      [\n        _createVNode('header', null, 'header'),\n        '\\n  ',\n        _createVNode('main', null, 'main'),\n        '\\n  ',\n        _createVNode('footer', null, 'footer'),\n      ],\n    ])\n  }\n}\n```\n\n这个 `Fragment` 是在 Vue 中定义的符号．\n\n换句话说，Fragment 不像 FragmentNode 那样表示为 AST，而是简单地作为 ElementNode 的标签．\n\n我们将在渲染器中实现 Fragment 的处理，类似于 Text．\n\n## 实现\n\nFragment 符号将在 runtime-core/vnode.ts 中实现．\n\n让我们将其作为 VNodeTypes 中的新类型添加．\n\n```ts\nexport type VNodeTypes = Component | typeof Text | typeof Fragment | string\n\nexport const Fragment = Symbol()\n```\n\n实现渲染器．\n\n在 patch 函数中为 fragment 添加分支．\n\n```ts\nif (type === Text) {\n  processText(n1, n2, container, anchor)\n} else if (shapeFlag & ShapeFlags.ELEMENT) {\n  processElement(n1, n2, container, anchor, parentComponent)\n} else if (type === Fragment) {\n  // 这里\n  processFragment(n1, n2, container, anchor, parentComponent)\n} else if (shapeFlag & ShapeFlags.COMPONENT) {\n  processComponent(n1, n2, container, anchor, parentComponent)\n} else {\n  // do nothing\n}\n```\n\n注意插入或删除元素通常应该用 anchor 作为标记来实现．\n\n顾名思义，anchor 表示 fragment 的开始和结束位置．\n\n起始元素由 VNode 中现有的 `el` 属性表示，但目前没有表示结束的属性．让我们添加它．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  anchor: HostNode | null // fragment anchor // 添加\n  // .\n  // .\n}\n```\n\n在挂载期间设置 anchor．\n\n在 mount/patch 中将 fragment 的结束作为 anchor 传递．\n\n```ts\nconst processFragment = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!\n  const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!\n\n  if (n1 == null) {\n    hostInsert(fragmentStartAnchor, container, anchor)\n    hostInsert(fragmentEndAnchor, container, anchor)\n    mountChildren(\n      n2.children as VNode[],\n      container,\n      fragmentEndAnchor,\n      parentComponent,\n    )\n  } else {\n    patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent)\n  }\n}\n```\n\n当 fragment 的元素在更新期间发生变化时要小心．\n\n```ts\nconst move = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const { type, children, el, shapeFlag } = vnode\n\n  // .\n\n  if (type === Fragment) {\n    hostInsert(el!, container, anchor)\n    for (let i = 0; i < (children as VNode[]).length; i++) {\n      move((children as VNode[])[i], container, anchor)\n    }\n    hostInsert(vnode.anchor!, container, anchor) // 插入 anchor\n    return\n  }\n  // .\n  // .\n  // .\n}\n```\n\n在卸载期间，也依赖 anchor 来删除元素．\n\n```ts\nconst remove = (vnode: VNode) => {\n  const { el, type, anchor } = vnode\n  if (type === Fragment) {\n    removeFragment(el!, anchor!)\n  }\n\n  // .\n  // .\n  // .\n}\n\nconst removeFragment = (cur: RendererNode, end: RendererNode) => {\n  let next\n  while (cur !== end) {\n    next = hostNextSibling(cur)! // ※ 将此添加到 nodeOps！\n    hostRemove(cur)\n    cur = next\n  }\n  hostRemove(end)\n}\n```\n\n## 测试\n\n我们之前编写的代码应该正确工作．\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n目前，我们不能使用像 v-for 这样的指令，所以我们不能编写在模板中使用 fragment 并改变元素数量的描述．\n\n让我们通过编写编译后的代码来模拟行为，看看它是如何工作的．\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\n// const App = defineComponent({\n//   template: `<header>header</header>\n//   <main>main</main>\n//   <footer>footer</footer>`,\n// });\n\nconst App = defineComponent({\n  setup() {\n    const list = ref([0])\n    const update = () => {\n      list.value = [...list.value, list.value.length]\n    }\n    return () =>\n      h(Fragment, {}, [\n        h('button', { onClick: update }, 'update'),\n        ...list.value.map(i => h('div', {}, i)),\n      ])\n  },\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n看起来工作正常！\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/030_fragment)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/035-comment.md",
    "content": "# 实现注释\n\n## 目标开发者接口\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `\n  <!-- this is header. -->\n  <header>header</header>\n\n  <!-- \n    this is main.\n    main content is here!\n  -->\n  <main>main</main>\n\n  <!-- this is footer -->\n  <footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n无需进一步解释．\n\n## AST 和解析器的实现\n\n关于如何实现注释，乍一看，似乎我们可以在解析时简单地忽略它．\n\n然而，在 Vue 中，模板中编写的注释会按原样作为 HTML 输出．\n\n换句话说，注释也需要被渲染，所以需要在 VNode 上有一个表示，编译器也需要输出该代码．\n此外，还需要一个生成注释节点的操作．\n\n首先，让我们实现 AST 和解析器．\n\n### AST\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  COMMENT,\n  // .\n  // .\n  // .\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT\n  content: string\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n```\n\n### 解析器\n\n现在，让我们抛出一个错误．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  // .\n  // .\n  // .\n  if (startsWith(s, '{{')) {\n    node = parseInterpolation(context)\n  } else if (s[0] === '<') {\n    if (s[1] === '!') {\n      // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n      if (startsWith(s, '<!--')) {\n        node = parseComment(context)\n      }\n    } else if (/[a-z]/i.test(s[1])) {\n      node = parseElement(context, ancestors)\n    }\n  }\n  // .\n  // .\n  // .\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context)\n  let content: string\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source)\n  if (!match) {\n    content = context.source.slice(4)\n    advanceBy(context, context.source.length)\n    throw new Error('EOF_IN_COMMENT') // TODO: 错误处理\n  } else {\n    if (match.index <= 3) {\n      throw new Error('ABRUPT_CLOSING_OF_EMPTY_COMMENT') // TODO: 错误处理\n    }\n    if (match[1]) {\n      throw new Error('INCORRECTLY_CLOSED_COMMENT') // TODO: 错误处理\n    }\n    content = context.source.slice(4, match.index)\n\n    const s = context.source.slice(0, match.index)\n    let prevIndex = 1,\n      nestedIndex = 0\n    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1)\n      if (nestedIndex + 4 < s.length) {\n        throw new Error('NESTED_COMMENT') // TODO: 错误处理\n      }\n      prevIndex = nestedIndex + 1\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1)\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n## 代码生成\n\n向 runtime-core 添加表示 Comment 的 VNode．\n\n```ts\nexport const Comment = Symbol()\nexport type VNodeTypes =\n  | string\n  | Component\n  | typeof Text\n  | typeof Comment\n  | typeof Fragment\n```\n\n实现一个名为 createCommentVNode 的函数并将其作为辅助函数公开．\n\n在 codegen 中，生成调用 createCommentVNode 的代码．\n\n```ts\nexport function createCommentVNode(text: string = ''): VNode {\n  return createVNode(Comment, null, text)\n}\n```\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    // .\n    // .\n    // .\n    case NodeTypes.COMMENT:\n      genComment(node, context)\n      break\n    // .\n    // .\n    // .\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)\n}\n```\n\n## 渲染\n\n让我们实现渲染器．\n\n像往常一样，在 patch 中分支 Comment 的情况，并在挂载时生成注释．\n\n关于 patch，由于这次是静态的，我不会做任何特殊的事情．（在代码中，它只是设置为按原样分配．）\n\n```ts\nconst patch = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const { type, ref, shapeFlag } = n2\n  if (type === Text) {\n    processText(n1, n2, container, anchor)\n  } else if (type === Comment) {\n    processCommentNode(n1, n2, container, anchor)\n  } //.\n  //.\n  //.\n}\n\nconst processCommentNode = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  if (n1 == null) {\n    hostInsert(\n      (n2.el = hostCreateComment((n2.children as string) || '')), // 在 nodeOps 端实现 hostCreateComment！\n      container,\n      anchor,\n    )\n  } else {\n    n2.el = n1.el\n  }\n}\n```\n\n好了，你现在应该已经实现了注释．让我们检查实际操作！\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/035_comment)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/040-v-if-and-structural-directive.md",
    "content": "# v-if 和结构指令\n\n现在让我们继续实现指令！\n\n最后，我们将实现 v-if．\n\n## v-if 指令与之前指令的区别\n\n到目前为止，我们已经实现了 v-bind 和 v-on 等指令．\n\n现在让我们实现 v-if，但 v-if 与这些指令略有不同．\n\n根据 Vue.js 官方文档关于编译时优化的摘录，\n\n> 在这种情况下，整个模板有一个单一的块，因为它不包含任何结构指令，如 v-if 和 v-for。\n\nhttps://vuejs.org/guide/extras/rendering-mechanism.html#tree-flattening\n\n如你所见，可以找到\"结构指令\"这个词．（你不必担心什么是 Tree Flattening，因为它将单独解释．）\n\n如前所述，v-if 和 v-for 被称为\"结构指令\"，是涉及结构的指令．\n\n在 Angular 的文档中，它们也被明确提及．\n\nhttps://angular.jp/guide/structural-directives\n\nv-if 和 v-for 是不仅改变元素的属性（以及事件的行为），还通过切换元素的存在或根据列表中项目的数量生成/删除元素来改变元素结构的指令．\n\n## 期望的开发者接口\n\n让我们考虑如何结合 v-if / v-else-if / v-else 来实现 FizzBuzz．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const n = ref(1)\n    const inc = () => {\n      n.value++\n    }\n\n    return { n, inc }\n  },\n\n  template: `\n    <button @click=\"inc\">inc</button>\n    <p v-if=\"n % 5 === 0 && n % 3 === 0\">FizzBuzz</p>\n    <p v-else-if=\"n % 5 === 0\">Buzz</p>\n    <p v-else-if=\"n % 3 === 0\">Fizz</p>\n    <p v-else>{{ n }}</p>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n首先，让我们考虑我们想要生成的代码．\n\n简单地说，v-if 和 v-else 被转换为如下的条件表达式：\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\n如你所见，我们正在向到目前为止实现的代码添加结构．\n\n要实现将 AST 转换为此类代码的转换器，我们需要进行一些修改．\n\n::: warning\n\n当前实现不处理空白和其他跳过，因此中间可能有不必要的文本节点．\n\n但是，v-if 的实现没有问题（你稍后会看到），所以现在请忽略它．\n\n:::\n\n## 结构指令的实现\n\n### 实现与结构相关的方法\n\n在实现 v-if 之前，让我们做一些准备．\n\n如前所述，v-if 和 v-for 是修改 AST 节点结构的结构指令．\n\n为了实现这一点，我们需要在基础转换器中实现几个方法．\n\n具体来说，我们将在 TransformContext 中实现以下三个方法：\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  replaceNode(node: TemplateChildNode): void // 添加\n  removeNode(node?: TemplateChildNode): void // 添加\n  onNodeRemoved(): void // 添加\n}\n```\n\n由于你已经在实现 traverseChildren，我认为你已经在跟踪当前父级和子级的索引．你可以使用它们来实现上述方法．\n\n<!-- NOTE: You may not need to implement this chapter yet. -->\n\n::: details 以防万一\n\n这部分：\n\n我认为你已经实现了它，但我会解释一下，以防万一，因为我在实现它的章节中没有详细解释．\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent // 这个\n    context.childIndex = i // 这个\n    traverseNode(child, context)\n  }\n}\n```\n\n:::\n\n```ts\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n\n    // 用给定节点替换当前节点和相应父级的子级\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node\n    },\n\n    // 从当前节点的父级的子级中删除给定节点\n    removeNode(node) {\n      const list = context.parent!.children\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1\n      if (!node || node === context.currentNode) {\n        // 当前节点被删除\n        context.currentNode = null\n        context.onNodeRemoved()\n      } else {\n        // 兄弟节点被删除\n        if (context.childIndex > removalIndex) {\n          context.childIndex--\n          context.onNodeRemoved()\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1)\n    },\n\n    // 这在使用 replaceNode 等时注册\n    onNodeRemoved: () => {},\n  }\n\n  return context\n}\n```\n\n现有实现也需要一些修改．调整 traverseChildren 以处理调用 removeNode 的情况．\n\n由于删除节点时索引会发生变化，因此在删除节点时减少索引．\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  let i = 0 // 这个\n  const nodeRemoved = () => {\n    i-- // 这个\n  }\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    context.onNodeRemoved = nodeRemoved // 这个\n    traverseNode(child, context)\n  }\n}\n```\n\n### createStructuralDirectiveTransform 的实现\n\n为了实现 v-if 和 v-for 等指令，我们将实现一个名为 createStructuralDirectiveTransform 的辅助函数．\n\n这些转换器只作用于 NodeTypes.ELEMENT，并将每个转换器的实现应用于 Node 拥有的 DirectiveNode．\n\n嗯，实现本身并不大，所以我认为如果你实际看到它会更容易理解．它看起来像这样：\n\n```ts\n// 每个转换器（v-if/v-for 等）都根据此接口实现。\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void)\n\nexport function createStructuralDirectiveTransform(\n  // 名称也支持正则表达式。\n  // 例如，在 v-if 的转换器中，假设接收类似 /^(if|else|else-if)$/ 的东西。\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name)\n    ? (n: string) => n === name\n    : (n: string) => name.test(n)\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      // 只作用于 NodeTypes.ELEMENT\n      const { props } = node\n      const exitFns = []\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i]\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          // 为匹配名称的 NodeTypes.DIRECTIVE 执行转换器\n          props.splice(i, 1)\n          i--\n          const onExit = fn(node, prop, context)\n          if (onExit) exitFns.push(onExit)\n        }\n      }\n      return exitFns\n    }\n  }\n}\n```\n\n## 实现 v-if\n\n### AST 实现\n\n准备工作到此为止已经完成．从这里开始，让我们实现 v-if．\n\n像往常一样，让我们从 AST 的定义开始，实现解析器．\n\n我想说，但这次似乎我们不需要解析器．\n\n相反，这次我们将考虑我们希望转换后的 AST 看起来如何，并实现转换器来相应地转换它．\n\n让我们看看开始时假设的编译代码．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\n可以看出，它最终被转换为条件表达式（三元运算符）．\n\n由于我们以前从未处理过条件表达式，似乎我们需要在 Codegen 的 AST 端处理这个．\n基本上，我们想要考虑三个信息（因为它是\"三元\"运算符）．\n\n- **条件**\n  这是 A ? B : C 中对应于 A 的部分．\n  用名称\"condition\"表示．\n- **条件匹配时的节点**\n  这是 A ? B : C 中对应于 B 的部分．\n  用名称\"consequent\"表示．\n- **条件不匹配时的节点**\n  这是 A ? B : C 中对应于 C 的部分．\n  用名称\"alternate\"表示．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION\n  test: JSChildNode\n  consequent: JSChildNode\n  alternate: JSChildNode\n  newline: boolean\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n\nexport function createConditionalExpression(\n  test: ConditionalExpression['test'],\n  consequent: ConditionalExpression['consequent'],\n  alternate: ConditionalExpression['alternate'],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  }\n}\n```\n\n我们将使用这些实现一个 AST 来表示 VIf 节点．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  IF,\n  IF_BRANCH,\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF\n  branches: IfBranchNode[]\n  codegenNode?: IfConditionalExpression\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall\n  alternate: VNodeCall | IfConditionalExpression\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH\n  condition: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  userKey?: AttributeNode | DirectiveNode\n}\n\nexport type ParentNode = RootNode | ElementNode | IfBranchNode\n```\n\n### 转换器的实现\n\n现在我们有了 AST，让我们实现生成此 AST 的转换器．\n\n想法是基于几个 `ElementNode` 生成一个 `IfNode`．\n\n所谓\"几个\"，在这种情况下，意味着如果有多个 `ElementNode`，我们需要生成一个包含从 `v-if` 到 `v-else` 语句的单个 `IfNode`．\n\n如果第一个 `v-if` 匹配，我们需要在检查后续节点是否为 `v-else-if` 或 `v-else` 的同时生成 `IfNode`．\n\n让我们首先实现整体结构，使用我们之前实现的 `createStructuralDirectiveTransform`．\n\n具体来说，由于我们最终想要用我们之前实现的 AST 填充 `codegenNode`，我们将在此转换器的 `onExit` 中生成 Node．\n\n```ts\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!)\n          parentCondition.alternate = createCodegenNodeForBranch(\n            branch,\n            context,\n          )\n        }\n      }\n    })\n  },\n)\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // TODO:\n}\n```\n\n```ts\n/// 用于生成 codegenNode 的函数\n\n// 为分支生成 codegenNode\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      // alternate 暂时设置为生成注释。\n      // 当遇到 v-else-if 或 v-else 时，它将被替换为目标 Node。\n      // 这是写 `parentCondition.alternate = createCodegenNodeForBranch(branch, context);` 的部分。\n      // 如果没有遇到 v-else-if 或 v-else，它将保持为 CREATE_COMMENT Node。\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', 'true']),\n    ) as IfConditionalExpression\n  } else {\n    return createChildrenCodegenNode(branch, context)\n  }\n}\n\nfunction createChildrenCodegenNode(\n  branch: IfBranchNode,\n  context: TransformContext,\n): VNodeCall {\n  // 只是从分支中提取 vnode call\n  const { children } = branch\n  const firstChild = children[0]\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall\n  return vnodeCall\n}\n\nfunction getParentCondition(\n  node: IfConditionalExpression,\n): IfConditionalExpression {\n  // 通过从节点追踪获取结束 Node\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate\n      } else {\n        return node\n      }\n    }\n  }\n}\n```\n\n在 `processIf` 中，执行更具体的 AST 节点转换．\n\n有 if / else-if / else 的情况，但让我们首先考虑 `if` 的情况．\n\n这非常简单．我们创建一个 IfNode 并执行 codegenNode 生成．\n此时，我们将当前 Node 生成为 IfBranch 并将其分配给 IfNode，然后用 IfNode 替换它．\n\n```\n- parent\n  - currentNode\n\n↓\n\n- parent\n  - IfNode\n    - IfBranch (currentNode)\n```\n\n这是改变结构的图像．\n\n```ts\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // 我们将提前在 exp 上运行 processExpression。\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)\n  }\n\n  if (dir.name === 'if') {\n    const branch = createIfBranch(node, dir)\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    }\n    context.replaceNode(ifNode)\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true)\n    }\n  } else {\n    // TODO:\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === 'else' ? undefined : dir.exp,\n    children: [node],\n  }\n}\n```\n\n让我们考虑除 v-if 之外的情况．\n\n我们将通过上下文从父级的子级遍历以获取兄弟节点．\n我们将循环遍历节点（从当前节点本身开始）并基于自身生成 IfBranch，将它们推入分支．\n在此过程中，注释和空文本将被删除．\n\n```ts\nif (dir.name === 'if') {\n  /** 省略 */\n} else {\n  const siblings = context.parent!.children\n  let i = siblings.indexOf(node)\n  while (i-- >= -1) {\n    const sibling = siblings[i]\n    if (sibling && sibling.type === NodeTypes.COMMENT) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (\n      sibling &&\n      sibling.type === NodeTypes.TEXT &&\n      !sibling.content.trim().length\n    ) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (sibling && sibling.type === NodeTypes.IF) {\n      context.removeNode()\n      const branch = createIfBranch(node, dir)\n      sibling.branches.push(branch)\n      const onExit = processCodegen && processCodegen(sibling, branch, false)\n      traverseNode(branch, context)\n      if (onExit) onExit()\n      context.currentNode = null\n    }\n    break\n  }\n}\n```\n\n如你所见，实际上 else-if 和 else 没有区别．\n\n即使在 AST 中，如果没有条件，它被定义为 else，所以没有什么特别需要考虑的．\n（在 `createIfBranch` 的 `dir.name === \"else\" ? undefined : dir.exp` 部分被吸收）\n\n重要的是在 `if` 时生成 `IfNode`，对于其他情况，只需将它们推入该 Node 的分支．\n\n通过这样，transformIf 的实现就完成了．我们只需要在周围进行一些调整．\n\n在 traverseNode 中，我们将为 IfNode 拥有的分支执行 traverseNode．\n\n我们还将 IfBranch 包含为 traverseChildren 的目标．\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  // .\n  // .\n  // .\n  switch (node.type) {\n    // .\n    // .\n    // 添加\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context)\n      }\n      break\n\n    case NodeTypes.IF_BRANCH: // 添加\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n}\n```\n\n最后，我们只需要在编译器中将 transformIf 注册为选项．\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformElement],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\n通过这样，转换器就实现了！\n\n剩下的就是实现 codegen，v-if 就完成了．我们快到了，让我们加油！\n\n### codegen 的实现\n\n剩下的很容易．只需基于 ConditionalExpression 的 Node 生成代码．\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.IF: // 不要忘记添加这个！\n      genNode(node.codegenNode!, context, option)\n      break\n    // .\n    // .\n    // .\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option)\n      break\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break\n  }\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node\n  const { push, indent, deindent, newline } = context\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context)\n  } else {\n    push(`(`)\n    genNode(test, context, option)\n    push(`)`)\n  }\n  needNewline && indent()\n  context.indentLevel++\n  needNewline || push(` `)\n  push(`? `)\n  genNode(consequent, context, option)\n  context.indentLevel--\n  needNewline && newline()\n  needNewline || push(` `)\n  push(`: `)\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION\n  if (!isNested) {\n    context.indentLevel++\n  }\n  genNode(alternate, context, option)\n  if (!isNested) {\n    context.indentLevel--\n  }\n  needNewline && deindent(true /* without newline */)\n}\n```\n\n像往常一样，我们只是基于 AST 生成条件表达式，所以没有什么特别困难的．\n\n## 完成！！\n\n嗯，自从我们有一个稍微胖的章节以来已经有一段时间了，但通过这样，v-if 的实现就完成了！（干得好！）\n\n让我们尝试真正运行它！！\n\n它工作正常！\n\n![v-if FizzBuzz result in the browser](/figures/50-basic-template-compiler/v-if/fizzbuzz-result.png)\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/040_v_if_and_structural_directive)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/050-v-for.md",
    "content": "# 支持 v-for 指令\n\n## 目标开发者接口\n\n现在，让我们继续指令的实现．这次，让我们尝试支持 v-for．\n\n嗯，我想对于那些之前使用过 Vue.js 的人来说，这是一个熟悉的指令．\n\nv-for 有各种语法．\n最基本的是循环遍历数组，但你也可以循环遍历其他东西，如字符串，对象键，范围等等．\n\nhttps://vuejs.org/v2/guide/list.html\n\n虽然有点长，但这次，让我们以以下开发者接口为目标：\n\n```vue\n<script>\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst genId = () => Math.random().toString(36).slice(2)\n\nconst FRUITS_FACTORIES = [\n  () => ({ id: genId(), name: 'apple', color: 'red' }),\n  () => ({ id: genId(), name: 'banana', color: 'yellow' }),\n  () => ({ id: genId(), name: 'grape', color: 'purple' }),\n]\n\nexport default {\n  setup() {\n    const fruits = ref([...FRUITS_FACTORIES].map(f => f()))\n    const addFruit = () => {\n      fruits.value.push(\n        FRUITS_FACTORIES[Math.floor(Math.random() * FRUITS_FACTORIES.length)](),\n      )\n    }\n    return { fruits, addFruit }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"addFruit\">add fruits!</button>\n\n  <!-- basic -->\n  <ul>\n    <li v-for=\"fruit in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- indexed -->\n  <ul>\n    <li v-for=\"(fruit, i) in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- destructuring -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">{{ name }}</span>\n    </li>\n  </ul>\n\n  <!-- object -->\n  <ul>\n    <li v-for=\"(value, key, idx) in fruits[0]\" :key=\"key\">\n      [{{ idx }}] {{ key }}: {{ value }}\n    </li>\n  </ul>\n\n  <!-- range -->\n  <ul>\n    <li v-for=\"n in 10\">{{ n }}</li>\n  </ul>\n\n  <!-- string -->\n  <ul>\n    <li v-for=\"c in 'hello'\">{{ c }}</li>\n  </ul>\n\n  <!-- nested -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">\n        <span v-for=\"n in 3\">{{ n }}</span>\n        <span>{{ name }}</span>\n      </span>\n    </li>\n  </ul>\n</template>\n```\n\n你可能会想，\"我们突然要实现这么多东西？这不可能！\"但不要担心，我会一步一步地解释．\n\n## 实现方法\n\n首先，让我们大致思考一下我们想要如何编译它，并考虑在实现时可能遇到的困难点．\n\n首先，让我们看看期望的编译结果．\n\n基本结构并不那么困难．我们将在 runtime-core 中实现一个名为 renderList 的辅助函数来渲染列表，并将其编译为表达式．\n\n示例 1：\n\n```html\n<!-- input -->\n<li v-for=\"fruit in fruits\" :key=\"fruit.id\">{{ fruit.name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\n示例 2：\n\n```html\n<!-- input -->\n<li v-for=\"(fruit, idx) in fruits\" :key=\"fruit.id\">\n  {{ idx }}: {{ fruit.name }}\n</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\n示例 3：\n\n```html\n<!-- input -->\n<li v-for=\"{ name, id } in fruits\" :key=\"id\">{{ name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, ({ name, id }) => h('li', { key: id }, name)),\n)\n```\n\n将来，作为 renderList 第一个参数传递的值预期不仅是数组，还可能是数字和对象．但是，现在让我们假设只期望数组．\\_renderList 函数本身的实现可以理解为类似于 Array.prototype.map 的东西．至于除数组之外的值，你只需要在 \\_renderList 中对它们进行规范化，所以现在让我们忘记它们（只关注数组）．\n\n现在，对于那些到目前为止已经实现了各种指令的人来说，实现这种编译器（转换器）应该不会太困难．\n\n## 关键实现点（困难点）\n\n困难点在于在 SFC（单文件组件）中使用它时．你还记得在 SFC 中使用的编译器和在浏览器中使用的编译器之间的区别吗？是的，就是使用 `_ctx` 解析表达式．\n\n在 v-for 中，用户定义的局部变量以各种形式出现，所以你需要正确地收集它们并跳过 rewriteIdentifiers．\n\n```ts\n// 错误示例\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // fruits 有前缀是可以的，因为它是从 _ctx 绑定的\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: _ctx.id }, // 这里有 _ctx 是不对的\n        _ctx.name, // 这里有 _ctx 是不对的\n      ),\n  ),\n)\n```\n\n```ts\n// 正确示例\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // fruits 有前缀是可以的，因为它是从 _ctx 绑定的\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: id }, // 这里不应该有 _ctx\n        name, // 这里不应该有 _ctx\n      ),\n  ),\n)\n```\n\n从示例 1 到 3，有各种局部变量的定义．\n\n你需要分析每个定义并收集要跳过的标识符．\n\n现在，让我们暂时搁置如何实现这一点，从大局开始实现．\n\n## AST 的实现\n\n现在，让我们像往常一样定义 AST．\n\n与 v-if 一样，我们将考虑转换后的 AST（无需实现解析器）．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  FOR, // [!code ++]\n  // .\n  // .\n  JS_FUNCTION_EXPRESSION, // [!code ++]\n}\n\nexport type ParentNode =\n  | RootNode\n  | ElementNode\n  | ForNode // [!code ++]\n  | IfBranchNode\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR\n  source: ExpressionNode\n  valueAlias: ExpressionNode | undefined\n  keyAlias: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  parseResult: ForParseResult // 稍后解释\n  codegenNode?: ForCodegenNode\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true\n  tag: typeof FRAGMENT\n  props: undefined\n  children: ForRenderListExpression\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST // 稍后解释\n  arguments: [ExpressionNode, ForIteratorExpression]\n}\n\n// 还支持函数表达式，因为回调函数用作 renderList 的第二个参数。\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n}\n\n// 在 v-for 的情况下，返回是固定的，所以它被表示为专门用于此目的的 AST。\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression // [!code ++]\n```\n\n关于 `RENDER_LIST`，像往常一样，将其添加到 `runtimeHelpers`．\n\n```ts\n// runtimeHelpers.ts\n// .\n// .\n// .\nexport const RENDER_LIST = Symbol() // [!code ++]\n\nexport const helperNameMap: Record<symbol, string> = {\n  // .\n  // .\n  [RENDER_LIST]: `renderList`, // [!code ++]\n  // .\n  // .\n}\n```\n\n至于 `ForParseResult`，其定义在 `transform/vFor` 中．\n\n```ts\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n```\n\n为了解释它们各自指的是什么，\n\n在 `v-for=\"(fruit, i) in fruits\"` 的情况下，\n\n- source: `fruits`\n- value: `fruit`\n- key: `i`\n- index: `undefined`\n\n`index` 是将对象应用于 `v-for` 时的第三个参数．\n\nhttps://vuejs.org/v2/guide/list.html#v-for-with-an-object\n\n![ForParseResult shape](/figures/50-basic-template-compiler/v-for/for-parse-result.svg)\n\n关于 `value`，如果你使用像 `{ id, name, color, }` 这样的解构赋值，它将有多个标识符．\n\n我们收集由 `value`，`key` 和 `index` 定义的标识符，并跳过添加前缀．\n\n## codegen 的实现\n\n虽然顺序有点颠倒，但让我们先实现 codegen，因为没有太多要讨论的．\n只需要做两件事：处理 `NodeTypes.FOR` 和函数表达式的 codegen（这是第一次出现）．\n\n```ts\nswitch (node.type) {\n  case NodeTypes.ELEMENT:\n  case NodeTypes.FOR: // [!code ++]\n  case NodeTypes.IF:\n  // .\n  // .\n  // .\n  case NodeTypes.JS_FUNCTION_EXPRESSION: // [!code ++]\n    genFunctionExpression(node, context, option) // [!code ++]\n    break // [!code ++]\n  // .\n  // .\n  // .\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context\n  const { params, returns, newline } = node\n\n  push(`(`, node)\n  if (isArray(params)) {\n    genNodeList(params, context, option)\n  } else if (params) {\n    genNode(params, context, option)\n  }\n  push(`) => `)\n  if (newline) {\n    push(`{`)\n    indent()\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `)\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option)\n    } else {\n      genNode(returns, context, option)\n    }\n  }\n  if (newline) {\n    deindent()\n    push(`}`)\n  }\n}\n```\n\n没有什么特别困难的．就这样结束了．\n\n## 转换器的实现\n\n### 准备工作\n\n在实现转换器之前，还有一些准备工作．\n\n正如我们在 `v-on` 中所做的，在 `v-for` 的情况下，执行 `processExpression` 的时机有点特殊（我们需要收集局部变量），所以我们在 `transformExpression` 中跳过它．\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (\n        dir.type === NodeTypes.DIRECTIVE &&\n        dir.name !== 'for' // [!code ++]\n      ) {\n        // .\n        // .\n        // .\n      }\n    }\n  }\n}\n```\n\n### 收集标识符\n\n现在，在我们继续主要实现之前，让我们思考如何收集标识符．\n\n这次，我们需要考虑不仅是像 `fruit` 这样的简单标识符，还有像 `{ id, name, color }` 这样的解构赋值．\n为此，似乎我们需要像往常一样使用 TreeWalker．\n\n目前，在 `processExpression` 函数中，实现是搜索标识符并向它们添加 `_ctx`．但是，这次我们只需要收集标识符而不添加任何东西．让我们实现这一点．\n\n首先，让我们准备一个地方来存储收集的标识符．由于如果每个 Node 都有它们对于 codegen 和其他目的会很方便，让我们向 AST 添加一个可以在每个 Node 上保存多个标识符的属性．\n\n目标是 `CompoundExpressionNode` 和 `SimpleExpressionNode`．\n\n像 `fruit` 这样的简单标识符将被添加到 `SimpleExpressionNode`，\n像 `{ id, name, color }` 这样的解构赋值将被添加到 `CompoundExpressionNode`．（在可视化方面，它将是一个复合表达式，如 `[\"{\", simpleExpr(\"id\"), \",\", simpleExpr(\"name\"), \",\", simpleExpr(\"color\"), \"}\"]`）\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[] // [!code ++]\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n  identifiers?: string[] // [!code ++]\n}\n```\n\n在 `processExpression` 函数中，让我们在这里实现收集标识符的逻辑，并通过将收集的标识符添加到转换器的上下文中来跳过添加前缀．\n\n目前，用于添加/删除标识符的函数被配置为接收单个标识符作为字符串，所以让我们将其更改为假设 `{ identifier: string[] }` 的形式．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  addIdentifiers(exp: ExpressionNode | string): void\n  removeIdentifiers(exp: ExpressionNode | string): void\n  // .\n  // .\n  // .\n}\n\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        addId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(addId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        addId(exp.content)\n      }\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        removeId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(removeId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        removeId(exp.content)\n      }\n    }\n  },\n  // .\n  // .\n  // .\n}\n```\n\n现在，让我们在 `processExpression` 函数中实现收集标识符的逻辑．\n\n在 `processExpression` 函数中，定义一个名为 `asParams` 的选项，如果设置为 true，实现跳过添加前缀并在 `node.identifiers` 中收集标识符的逻辑．\n\n`asParams` 旨在引用在 `renderList` 的回调函数中定义的参数（局部变量）．\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false, // [!code ++]\n) {\n  // .\n  if (isSimpleIdentifier(rawExp)) {\n    const isScopeVarReference = ctx.identifiers[rawExp]\n    if (\n      !asParams && // [!code ++]\n      !isScopeVarReference\n    ) {\n      node.content = rewriteIdentifier(rawExp)\n    } // [!code ++]\n    return node\n\n    // .\n  }\n}\n```\n\n这就是简单标识符的结束．问题在于其他情况．\n\n为此，我们将使用在 `babelUtils` 中实现的 `walkIdentifiers`．\n\n由于我们假设定义为函数参数的局部变量，我们将在此函数中将它们转换为\"函数参数\"，并在 `walkIdentifier` 中将它们作为 Function params 搜索．\n\n```ts\n// 将 asParams 转换为类似函数参数的形式\nconst source = `(${rawExp})${asParams ? `=>{}` : ``}`\n\n// walkIdentifiers 稍微复杂一些。\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  // .\n\n  ;(walk as any)(root, {\n    // prettier-ignore\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n\n      } else if (isFunctionType(node)) {\n        // 稍后解释（在此函数内的 knownIds 中收集标识符）\n        walkFunctionParams(node, (id) =>\n          markScopeIdentifier(node, id, knownIds)\n        );\n      }\n    },\n  })\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)\n}\n```\n\n我们在这里做的只是如果 node 是函数则遍历参数，并将标识符收集到 `identifiers` 中．\n\n在 `walkIdentifiers` 的调用者中，我们定义 `knownIds` 并将其与 `knownIds` 一起传递给 `walkIdentifiers` 以收集标识符．\n\n在 `walkIdentifiers` 中收集后，最后，在生成 CompoundExpression 时基于 `knownIds` 生成标识符．\n\n```ts\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers)\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // 传递\n  parentStack,\n)\n\n// .\n// .\n// .\n\nret.identifiers = Object.keys(knownIds) // 基于 knownIds 生成标识符\nreturn ret\n```\n\n虽然文件有点乱序，但 `walkFunctionParams` 和 `markScopeIdentifier` 只是遍历参数并将 `Node.name` 添加到 `knownIds`．\n\n```ts\nexport function walkFunctionParams(\n  node: Function,\n  onIdent: (id: Identifier) => void,\n) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id)\n    }\n  }\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return\n  }\n  if (name in knownIds) {\n    knownIds[name]++\n  } else {\n    knownIds[name] = 1\n  }\n  ;(node.scopeIds || (node.scopeIds = new Set())).add(name)\n}\n```\n\n有了这个，我们应该能够收集标识符．让我们使用这个实现 `transformFor` 并完成 v-for 指令！\n\n### transformFor\n\n现在我们已经克服了障碍，让我们像往常一样使用我们拥有的东西实现转换器．\n还有一点点，让我们加油！\n\n像 v-if 一样，这也涉及结构，所以让我们使用 `createStructuralDirectiveTransform` 来实现它．\n\n我认为如果我用代码写解释会更容易理解，所以我将在下面提供带有解释的代码．但是，请在查看这个之前尝试通过阅读源代码自己实现它！\n\n```ts\n// 这是主要结构的实现，类似于 v-if。\n// 它在适当的地方执行 processFor 并在适当的地方生成 codegenNode。\n// processFor 是最复杂的实现。\nexport const transformFor = createStructuralDirectiveTransform(\n  'for',\n  (node, dir, context) => {\n    return processFor(node, dir, context, forNode => {\n      // 如预期的那样，生成调用 renderList 的代码。\n      const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n        forNode.source,\n      ]) as ForRenderListExpression\n\n      // 为作为 v-for 容器的 Fragment 生成 codegenNode。\n      forNode.codegenNode = createVNodeCall(\n        context,\n        context.helper(FRAGMENT),\n        undefined,\n        renderExp,\n      ) as ForCodegenNode\n\n      // codegen 过程（在 processFor 中的解析和标识符收集之后执行）\n      return () => {\n        const { children } = forNode\n        const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall\n\n        renderExp.arguments.push(\n          createFunctionExpression(\n            createForLoopParams(forNode.parseResult),\n            childBlock,\n            true /* force newline */,\n          ) as ForIteratorExpression,\n        )\n      }\n    })\n  },\n)\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  // 解析 v-for 的表达式。\n  // 在 parseResult 阶段，每个 Node 的标识符已经被收集。\n  const parseResult = parseForExpression(\n    dir.exp as SimpleExpressionNode,\n    context,\n  )\n\n  const { addIdentifiers, removeIdentifiers } = context\n\n  const { source, value, key, index } = parseResult!\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  }\n\n  // 用 forNode 替换 Node。\n  context.replaceNode(forNode)\n\n  if (!context.isBrowser) {\n    // 将收集的标识符添加到上下文中。\n    value && addIdentifiers(value)\n    key && addIdentifiers(key)\n    index && addIdentifiers(index)\n  }\n\n  // 生成代码（这允许跳过向局部变量添加前缀）\n  const onExit = processCodegen && processCodegen(forNode)\n\n  return () => {\n    value && removeIdentifiers(value)\n    key && removeIdentifiers(key)\n    index && removeIdentifiers(index)\n\n    if (onExit) onExit()\n  }\n}\n\n// 使用正则表达式解析给定给 v-for 的表达式。\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/\nconst stripParensRE = /^\\(|\\)$/g\n\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc\n  const exp = input.content\n  const inMatch = exp.match(forAliasRE)\n\n  if (!inMatch) return\n\n  const [, LHS, RHS] = inMatch\n  const result: ForParseResult = {\n    source: createAliasExpression(\n      loc,\n      RHS.trim(),\n      exp.indexOf(RHS, LHS.length),\n    ),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  }\n\n  if (!context.isBrowser) {\n    result.source = processExpression(\n      result.source as SimpleExpressionNode,\n      context,\n    )\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, '').trim()\n  const iteratorMatch = valueContent.match(forIteratorRE)\n  const trimmedOffset = LHS.indexOf(valueContent)\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, '').trim()\n    const keyContent = iteratorMatch[1].trim()\n    let keyOffset: number | undefined\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)\n      result.key = createAliasExpression(loc, keyContent, keyOffset)\n      if (!context.isBrowser) {\n        // 如果不在浏览器模式下，将 asParams 设置为 true 并收集 key 的标识符。\n        result.key = processExpression(result.key, context, true)\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim()\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key\n              ? keyOffset! + keyContent.length\n              : trimmedOffset + valueContent.length,\n          ),\n        )\n        if (!context.isBrowser) {\n          // 如果不在浏览器模式下，将 asParams 设置为 true 并收集 index 的标识符。\n          result.index = processExpression(result.index, context, true)\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset)\n    if (!context.isBrowser) {\n      // 如果不在浏览器模式下，将 asParams 设置为 true 并收集 value 的标识符。\n      result.value = processExpression(result.value, context, true)\n    }\n  }\n\n  return result\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(\n    content,\n    false,\n    getInnerRange(range, offset, content.length),\n  )\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs])\n}\n\nfunction createParamsList(\n  args: (ExpressionNode | undefined)[],\n): ExpressionNode[] {\n  let i = args.length\n  while (i--) {\n    if (args[i]) break\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))\n}\n```\n\n现在，剩下的部分是实际包含在编译代码中的 renderList 的实现，以及注册转换器的实现．如果我们能实现这些，v-for 应该就能工作了！\n\n让我们尝试运行它！\n\n![v-for result in the browser](/figures/50-basic-template-compiler/v-for/v-for-result.png)\n\n看起来进展顺利．\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/050_v_for)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/070-resolve-component.md",
    "content": "# 解析组件\n\n实际上，我们的 chibivue 模板还无法解析组件．\n让我们在这里实现它，因为 Vue.js 提供了几种解析组件的方法．\n\n首先，让我们回顾一些解析方法．\n\n## 组件的解析方法\n\n### 1. Components 选项（局部注册）\n\n这可能是解析组件最简单的方法．\n\nhttps://vuejs.org/api/options-misc.html#components\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: {\n    MyComponent,\n    MyComponent2: MyComponent,\n  },\n}\n</script>\n\n<template>\n  <MyComponent />\n  <MyComponent2 />\n</template>\n```\n\n在 components 选项对象中指定的键名成为可以在模板中使用的组件名称．\n\n### 2. 在应用上注册（全局注册）\n\n您可以通过使用创建的 Vue 应用程序的 `.component()` 方法来注册可在整个应用程序中使用的组件．\n\nhttps://vuejs.org/guide/components/registration.html#global-registration\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({})\n\napp\n  .component('ComponentA', ComponentA)\n  .component('ComponentB', ComponentB)\n  .component('ComponentC', ComponentC)\n```\n\n### 3. 动态组件 + is 属性\n\n通过使用 is 属性，您可以动态切换组件．\n\nhttps://vuejs.org/api/built-in-special-elements.html#component\n\n```vue\n<script>\nimport Foo from './Foo.vue'\nimport Bar from './Bar.vue'\n\nexport default {\n  components: { Foo, Bar },\n  data() {\n    return {\n      view: 'Foo',\n    }\n  },\n}\n</script>\n\n<template>\n  <component :is=\"view\" />\n</template>\n```\n\n### 4. 在 script setup 中导入\n\n在 script setup 中，您可以直接使用导入的组件．\n\n```vue\n<script setup>\nimport MyComponent from './MyComponent.vue'\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n此外，还有异步组件，嵌入式组件和 `component` 标签，但这次我将尝试处理上述两种（1，2）．\n\n关于 3，如果 1 和 2 可以处理它，那只是一个扩展．至于 4，由于 script setup 尚未实现，我们将暂时搁置．\n\n## 基本方法\n\n解析组件的基本方法如下：\n\n- 在某个地方，存储模板中使用的名称和组件记录．\n- 使用辅助函数根据名称解析组件．\n\n形式 1 和形式 2 都只是存储名称和组件记录，唯一的区别是它们注册的位置．  \n如果您有记录，您可以在必要时从名称解析组件，因此两种实现都将类似．\n\n首先，让我们看一下预期的代码和编译结果．\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default defineComponent({\n  components: { MyComponent },\n})\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n```js\n// 编译结果\n\nfunction render(_ctx) {\n  const {\n    resolveComponent: _resolveComponent,\n    createVNode: _createVNode,\n    Fragment: _Fragment,\n  } = ChibiVue\n\n  const _component_MyComponent = _resolveComponent('MyComponent')\n\n  return _createVNode(_Fragment, null, _createVNode(_component_MyComponent))\n}\n```\n\n看起来是这样的．\n\n## 实现\n\n### AST\n\n为了生成解析组件的代码，我们需要知道\"MyComponent\"是一个组件．  \n在解析阶段，我们处理标签名称并在 AST 上将其分为常规 Element 和 Component．\n\n首先，让我们考虑 AST 的定义．  \nComponentNode 与常规 Element 一样，具有 props 和 children．  \n在将这些公共部分合并为 `BaseElementNode` 的同时，我们将现有的 `ElementNode` 重命名为 `PlainElementNode`，  \n并使 `ElementNode` 成为 `PlainElementNode` 和 `ComponentNode` 的联合．\n\n```ts\n// compiler-core/ast.ts\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  tagType: ElementTypes\n  isSelfClosing: boolean\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT\n  codegenNode: VNodeCall | undefined\n}\n```\n\n内容与之前相同，但我们通过 `tagType` 区分它们并将它们视为单独的 AST．  \n我们将在转换阶段使用它来添加辅助函数等．\n\n### 解析器\n\n接下来，让我们实现解析器来生成上述 AST．  \n基本上，我们只需要根据标签名称确定 `tagType`．\n\n问题是如何确定它是 Element 还是 Component．\n\n基本思路很简单：只需确定它是否是\"原生标签\"．\n\n・  \n・  \n・\n\n\"等等，等等，这不是我要问的．我们实际上如何实现它？\"\n\n是的，这是一种暴力方法．我们预定义原生标签名称列表并确定它是否匹配．  \n至于应该枚举哪些项目，所有这些都应该写在规范中，所以我们将信任它并使用它．\n\n如果有问题的话，\"什么是原生标签\"可能因环境而异．  \n在这种情况下，它是浏览器．我的意思是\"compiler-core 不应该依赖于环境\"．  \n到目前为止，我们已经在 compiler-dom 中实现了这样的 DOM 依赖实现，这个枚举也不例外．\n\n考虑到这一点，我们将实现它，以便可以从解析器外部注入\"是否为原生标签\"的函数作为选项，考虑到未来的可能性并使其易于在以后添加各种选项．\n\n```ts\ntype OptionalOptions = 'isNativeTag' // | TODO: Add more in the future (maybe)\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>\n\nexport interface ParserContext {\n  // .\n  // .\n  options: MergedParserOptions // [!code ++]\n  // .\n  // .\n}\n\nfunction createParserContext(\n  content: string,\n  rawOptions: ParserOptions, // [!code ++]\n): ParserContext {\n  const options = Object.assign({}, defaultParserOptions) // [!code ++]\n\n  let key: keyof ParserOptions // [!code ++]\n  // prettier-ignore\n  for (key in rawOptions) { // [!code ++]\n    options[key] = // [!code ++]\n      rawOptions[key] === undefined // [!code ++]\n        ? defaultParserOptions[key] // [!code ++]\n        : rawOptions[key]; // [!code ++]\n  } // [!code ++]\n\n  // .\n  // .\n  // .\n}\n\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {}, // [!code ++]\n): RootNode => {\n  const context = createParserContext(\n    content,\n    options, // [!code ++]\n  )\n  const children = parseChildren(context, [])\n  return createRoot(children)\n}\n```\n\n现在，在 compiler-dom 中，我们将枚举原生标签名称并将它们作为选项传递．\n\n虽然我提到了 compiler-dom，但枚举本身是在 shared/domTagConfig.ts 中完成的．\n\n```ts\nimport { makeMap } from './makeMap'\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +\n  'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +\n  'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +\n  'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +\n  'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +\n  'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +\n  'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +\n  'option,output,progress,select,textarea,details,dialog,menu,' +\n  'summary,template,blockquote,iframe,tfoot'\n\nexport const isHTMLTag = makeMap(HTML_TAGS)\n```\n\n看起来相当可怕，不是吗？\n\n但这是正确的实现．\n\nhttps://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/shared/src/domTagConfig.ts#L6\n\n创建 compiler-dom/parserOptions.ts 并将其传递给编译器．\n\n```ts\n// compiler-dom/parserOptions.ts\n\nimport { ParserOptions } from '../compiler-core'\nimport { isHTMLTag, isSVGTag } from '../shared/domTagConfig'\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),\n}\n```\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(\n    template,\n    Object.assign(\n      {},\n      parserOptions, // [!code ++]\n      defaultOption,\n      {\n        directiveTransforms: DOMDirectiveTransforms,\n      },\n    ),\n  )\n}\n```\n\n解析器的实现已完成，所以我们现在将继续实现其余部分．\n\n其余部分非常简单．我们只需要确定它是否是组件并分配一个 tagType．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // .\n  // .\n  let tagType = ElementTypes.ELEMENT // [!code ++]\n  // prettier-ignore\n  if (isComponent(tag, context)) { // [!code ++]\n    tagType = ElementTypes.COMPONENT;// [!code ++]\n  } // [!code ++]\n\n  return {\n    // .\n    tagType, // [!code ++]\n    // .\n  }\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options\n  if (\n    // NOTE: 在 Vue.js 中，以大写字母开头的标签被视为组件。\n    // ref: https://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/compiler-core/src/parse.ts#L662\n    /^[A-Z]/.test(tag) ||\n    (options.isNativeTag && !options.isNativeTag(tag))\n  ) {\n    return true\n  }\n}\n```\n\n有了这个，解析器和 AST 就完成了．我们现在将继续使用这些来实现转换和代码生成．\n\n### 转换\n\n在转换中需要做的事情非常简单．\n\n在 transformElement 中，如果 Node 是 ComponentNode，我们只需要进行轻微的转换．\n\n此时，我们还在上下文中注册组件．\n这样做是为了我们可以在代码生成期间集体解析它．\n如后面提到的，组件将在代码生成中作为资产集体解析．\n\n```ts\n// compiler-core/transforms/transformElement.ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    // .\n    // .\n\n    const isComponent = node.tagType === ElementTypes.COMPONENT // [!code ++]\n\n    const vnodeTag = isComponent // [!code ++]\n      ? resolveComponentType(node as ComponentNode, context) // [!code ++]\n      : `\"${tag}\"` // [!code ++]\n\n    // .\n    // .\n  }\n}\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node\n  context.helper(RESOLVE_COMPONENT)\n  context.components.add(tag) // 稍后解释\n  return toValidAssetId(tag, `component`)\n}\n```\n\n```ts\n// util.ts\nexport function toValidAssetId(\n  name: string,\n  type: 'component', // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === '-' ? '_' : name.charCodeAt(replaceValue).toString()\n  })}`\n}\n```\n\n我们还确保在上下文中注册它．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  components: Set<string> // [!code ++]\n  // .\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  {\n    nodeTransforms = [],\n    directiveTransforms = {},\n    isBrowser = false,\n  }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    components: new Set(), // [!code ++]\n    // .\n  }\n}\n```\n\n然后，上下文中的所有组件都在目标组件的 RootNode 中注册．\n\n```ts\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT\n  children: TemplateChildNode[]\n  codegenNode?: TemplateChildNode | VNodeCall\n  helpers: Set<symbol>\n  components: string[] // [!code ++]\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  createRootCodegen(root, context)\n  root.helpers = new Set([...context.helpers.keys()])\n  root.components = [...context.components] // [!code ++]\n}\n```\n\n有了这个，剩下的就是在代码生成中使用 RootNode.components．\n\n### 代码生成\n\n代码只是通过将名称传递给辅助函数来生成代码以进行解析，就像我们在开始时看到的编译结果一样．我们将其抽象为\"资产\"以供将来考虑．\n\n```ts\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  // .\n  // .\n  genFunctionPreamble(ast, context) // NOTE: 将来将此移到函数外部\n\n  // prettier-ignore\n  if (ast.components.length) { // [!code ++]\n    genAssets(ast.components, \"component\", context); // [!code ++]\n    newline(); // [!code ++]\n    newline(); // [!code ++]\n  } // [!code ++]\n\n  push(`return `)\n  // .\n  // .\n}\n\nfunction genAssets(\n  assets: string[],\n  type: 'component' /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === 'component') {\n    const resolver = helper(RESOLVE_COMPONENT)\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i]\n\n      push(\n        `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(\n          id,\n        )})`,\n      )\n      if (i < assets.length - 1) {\n        newline()\n      }\n    }\n  }\n}\n```\n\n### runtime-core 端的实现\n\n现在我们已经生成了所需的代码，让我们转到 runtime-core 中的实现．\n\n#### 为组件添加\"component\"作为选项\n\n这很简单，只需将其添加到选项中．\n\n```ts\nexport type ComponentOptions<\n  // .\n  // .\n> = {\n  // .\n  components?: Record<string, Component>\n  // .\n}\n```\n\n#### 为应用添加\"components\"作为选项\n\n这也很简单．\n\n```ts\nexport interface AppContext {\n  // .\n  components: Record<string, Component> // [!code ++]\n  // .\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    // .\n    components: {}, // [!code ++]\n    // .\n  }\n}\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    // .\n    const app: App = (context.app = {\n      // .\n      // prettier-ignore\n      component(name: string, component: Component): any { // [!code ++]\n        context.components[name] = component; // [!code ++]\n        return app; // [!code ++]\n      },\n    })\n  }\n}\n```\n\n#### 实现从上述两者解析组件的函数\n\n这里没有什么特别需要解释的．\n它搜索本地和全局注册的组件，并返回组件．\n如果找不到，它将名称原样返回作为回退．\n\n```ts\n// runtime-core/helpers/componentAssets.ts\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance // 稍后解释\n  if (instance) {\n    const Component = instance.type\n    const res =\n      // 本地注册\n      resolve((Component as ComponentOptions).components, name) ||\n      // 全局注册\n      resolve(instance.appContext.components, name)\n    return res\n  }\n\n  return name\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry &&\n    (registry[name] ||\n      registry[camelize(name)] ||\n      registry[capitalize(camelize(name))])\n  )\n}\n```\n\n需要注意的一点是 `currentRenderingInstance`．\n\n为了在 `resolveComponent` 中遍历本地注册的组件，我们需要访问当前正在渲染的组件．\n（我们想要搜索正在渲染的组件的 `components` 选项）\n\n考虑到这一点，让我们准备 `currentRenderingInstance` 并在渲染时更新它．\n\n```ts\n// runtime-core/componentRenderContexts.ts\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance\n  currentRenderingInstance = instance\n  return prev\n}\n```\n\n```ts\n// runtime-core/renderer.ts\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const componentUpdateFn = () => {\n    // .\n    // .\n    const prev = setCurrentRenderingInstance(instance) // [!code ++]\n    const subTree = (instance.subTree = normalizeVNode(render(proxy!))) // [!code ++]\n    setCurrentRenderingInstance(prev) // [!code ++]\n    // .\n    // .\n  }\n  // .\n  // .\n}\n```\n\n## 让我们试试看\n\n太好了！我们终于可以解析组件了．\n\n让我们尝试在 playground 中运行它！\n\n```ts\nimport { createApp } from 'chibivue'\n\nimport App from './App.vue'\nimport Counter from './components/Counter.vue'\n\nconst app = createApp(App)\napp.component('GlobalCounter', Counter)\napp.mount('#app')\n```\n\nApp.vue\n\n```vue\n<script>\nimport Counter from './components/Counter.vue'\n\nimport { defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  components: { Counter },\n})\n</script>\n\n<template>\n  <Counter />\n  <Counter />\n  <GlobalCounter />\n</template>\n```\n\ncomponents/Counter.vue\n\n```vue\n<script>\nimport { ref, defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n})\n</script>\n\n<template>\n  <button @click=\"count++\">count: {{ count }}</button>\n</template>\n```\n\n![resolveComponent result in the browser](/figures/50-basic-template-compiler/resolve-component/resolve-components-result.png)\n\n看起来工作正常！太好了！\n\n到此为止的源代码：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/060_resolve_components)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/080-component-slot-outlet.md",
    "content": "# 组件插槽\n\n## 期望的开发者接口\n\n我们已经有了基本组件系统插槽实现的运行时实现．\\\n但是，我们仍然无法在模板中处理插槽．\n\n我们希望处理如下的 SFC：\\\n（虽然我们说 SFC，但实际上是模板编译器的实现．）\n\n```vue\n<!-- Comp.vue -->\n<template>\n  <p><slot name=\"default\" /></p>\n</template>\n```\n\n```vue\n<!-- App.vue -->\n<script>\nimport Comp from './Comp.vue'\nexport default {\n  components: {\n    Comp,\n  },\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n}\n</script>\n\n<template>\n  <Comp>\n    <template #default>\n      <button @click=\"count++\">count is: {{ count }}</button>\n    </template>\n  </Comp>\n</template>\n```\n\nVue.js 中有几种类型的插槽：\n\n- 默认插槽\n- 具名插槽\n- 作用域插槽\n\n但是，正如您可能已经从运行时实现中了解到的，这些都只是回调函数．让我们回顾一下以防万一．\n\n像上面这样的组件被转换为如下的渲染函数．\n\n```js\nh(Comp, null, {\n  default: () =>\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n})\n\n```\n\n在模板中，`name=\"default\"` 属性可以省略，但在运行时，它仍然会被视为名为 `default` 的插槽．我们将在完成具名插槽的实现后实现默认插槽的编译器．\n\n## 实现编译器（插槽定义）\n\n像往常一样，我们将实现解析和代码生成过程，但这次我们将处理插槽定义和插槽插入．\n\n首先，让我们专注于插槽定义．这是在子组件端表示为 `<slot name=\"my-slot\"/>` 的部分．\n\n在运行时，我们将准备一个名为 `renderSlot` 的辅助函数，它将通过组件实例（通过 `ctx.$slot`）插入的插槽及其名称作为参数．源代码将被编译为如下内容：\n\n```js\n_renderSlot(_ctx.$slots, \"my-slot\")\n```\n\n我们将在 AST 中将插槽定义表示为名为 `SlotOutletNode` 的节点．\\\n将以下定义添加到 `ast.ts`．\n\n```ts\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT, // [!code ++]\n}\n\n// ...\n\nexport type ElementNode = \n  | PlainElementNode \n  | ComponentNode \n  | SlotOutletNode // [!code ++]\n\n// ...\n\nexport interface SlotOutletNode extends BaseElementNode { // [!code ++]\n  tagType: ElementTypes.SLOT // [!code ++]\n  codegenNode: RenderSlotCall | undefined // [!code ++]\n} // [!code ++]\n\nexport interface RenderSlotCall extends CallExpression { // [!code ++]\n  callee: typeof RENDER_SLOT // [!code ++]\n  // $slots, name // [!code ++]\n  arguments: [string, string | ExpressionNode] // [!code ++]\n} // [!code ++]\n```\n\n让我们编写解析过程来生成这个 AST．\n\n在 `parse.ts` 中，任务很简单：在解析标签时，如果是 `\"slot\"`，将其更改为 `ElementTypes.SLOT`．\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // ...\n  let tagType = ElementTypes.ELEMENT\n  if (tag === 'slot') { // [!code ++]\n    tagType = ElementTypes.SLOT // [!code ++]\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT\n  }\n}\n```\n\n现在我们已经到了这一点，下一步是实现转换器来生成 `codegenNode`．\\\n我们需要为辅助函数创建一个 `JS_CALL_EXPRESSION`．\n\n作为预备步骤，将 `RENDER_SLOT` 添加到 `runtimeHelper.ts`．\n\n```ts\n// ...\nexport const RENDER_LIST = Symbol()\nexport const RENDER_SLOT = Symbol() // [!code ++]\nexport const MERGE_PROPS = Symbol()\n// ...\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: 'renderSlot', // [!code ++]\n  [MERGE_PROPS]: 'mergeProps',\n  // ...\n}\n```\n\n我们将实现一个名为 `transformSlotOutlet` 的新转换器．\\\n任务非常简单：当遇到 `ElementType.SLOT` 时，我们在 `node.props` 中搜索 `name` 并为 `RENDER_SLOT` 生成一个 `JS_CALL_EXPRESSION`．\\\n我们还考虑名称被绑定的情况，例如 `:name=\"slotName\"`．\n\n由于它很直接，这里是完整的转换器代码（请通读）．\n\n```ts\nimport { camelize } from '../../shared'\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from '../ast'\nimport { RENDER_SLOT } from '../runtimeHelpers'\nimport type { NodeTransform, TransformContext } from '../transform'\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node\n    const { slotName } = processSlotOutlet(node, context)\n    const slotArgs: CallExpression['arguments'] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ]\n\n    node.codegenNode = createCallExpression(\n      context.helper(RENDER_SLOT),\n      slotArgs,\n      loc,\n    )\n  }\n}\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`\n\n  const nonNameProps = []\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === 'name') {\n          slotName = JSON.stringify(p.value.content)\n        } else {\n          p.name = camelize(p.name)\n          nonNameProps.push(p)\n        }\n      }\n    } else {\n      if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {\n        if (p.exp) slotName = p.exp\n      } else {\n        if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content)\n        }\n        nonNameProps.push(p)\n      }\n    }\n  }\n\n  return { slotName }\n}\n```\n\n将来，我们还将在这里添加作用域插槽的属性探索．\n\n需要注意的一点是 `<slot />` 元素也会被 `transformElement` 捕获，所以我们将添加一个实现，在遇到 `ElementTypes.SLOT` 时跳过它．\n\n这是 `transformElement.ts`．\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if ( // [!code ++]\n      !( // [!code ++]\n        node.type === NodeTypes.ELEMENT && // [!code ++]\n        (node.tagType === ElementTypes.ELEMENT || // [!code ++]\n          node.tagType === ElementTypes.COMPONENT) // [!code ++]\n      ) // [!code ++]\n    ) { // [!code ++]\n      return // [!code ++]\n    } // [!code ++]\n\n    // ...\n  }\n}\n```\n最后，通过在 `compile.ts` 中注册 `transformSlotOutlet`，应该可以进行编译．\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformIf,\n      transformFor,\n      transformExpression,\n      transformSlotOutlet, // [!code ++]\n      transformElement,\n    ],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\n我们还没有实现运行时函数 `renderSlot`，所以我们将最后做这件事来完成插槽定义的实现．\n\n让我们实现 `packages/runtime-core/helpers/renderSlot.ts`．\n\n```ts\nimport { Fragment, type VNode, createVNode } from '../vnode'\nimport type { Slots } from '../componentSlots'\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name]\n  if (!slot) {\n    slot = () => []\n  }\n\n  return createVNode(Fragment, {}, slot())\n}\n```\n\n插槽定义的实现现在已完成．\\\n接下来，让我们实现插槽插入端的编译器！\n\n到此为止的源代码：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/080_component_slot_outlet)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/080-slot.md",
    "content": "---\nwip: true\n---\n\n# 插槽\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/085-component-slot-insert.md",
    "content": "# 支持插槽（使用）\n\n## 插槽插入\n\n接下来是插槽插入端的实现．\\\n这是在父组件端表示为 `<template #slot-name>` 的部分的编译．\n\n正如开头所解释的，插槽被编译为如下代码．\n\n```js\nh(Comp, null, {\n  default: _withCtx(() => [\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n  ]),\n})\n```\n\n也就是说，组件的子元素被作为 `SlotsExpression`（ObjectExpression）处理，每个插槽作为 `FunctionExpression` 生成，并用 `withCtx` 包装．\n\n## withCtx 的作用\n\n`withCtx` 是一个辅助函数，用于在正确的组件实例上下文中执行插槽函数．这确保了插槽内的响应式依赖被追踪到正确的组件．\n\n```ts\nexport function withCtx(\n  fn: Function,\n  ctx: ComponentInternalInstance | null = currentRenderingInstance,\n) {\n  if (!ctx) return fn;\n\n  const renderFnWithContext = (...args: any[]) => {\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    try {\n      return fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n    }\n  };\n\n  return renderFnWithContext;\n}\n```\n\n## 更新 AST\n\n首先，让我们更新 AST 定义．\\\n添加一个名为 `SlotsExpression` 的类型，并在 `FunctionExpression` 中添加一个 `isSlot` 标志来表示它是一个插槽函数．\n\n```ts\n// SlotsExpression is an ObjectExpression that represents the slots object\n// passed to a component. e.g., { default: () => [...], header: () => [...] }\nexport interface SlotsExpression extends ObjectExpression {}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n  isSlot?: boolean // [!code ++]\n}\n```\n\n此外，将 `SlotsExpression` 添加到 `VNodeCall` 的 `children` 类型中．\n\n```ts\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[]\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | SlotsExpression // [!code ++]\n    | undefined\n}\n```\n\n## 添加辅助函数\n\n在 `runtimeHelpers.ts` 中添加 `WITH_CTX`．\n\n```ts\nexport const WITH_CTX = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_CTX]: 'withCtx',\n}\n```\n\n## 添加工具函数\n\n在 `utils.ts` 中添加 `findDir` 和 `isTemplateNode` 工具函数．\n\n```ts\nexport function isTemplateNode(\n  node: RootNode | TemplateChildNode,\n): node is PlainElementNode & { tag: 'template' } {\n  return (\n    node.type === NodeTypes.ELEMENT &&\n    node.tagType === ElementTypes.ELEMENT &&\n    node.tag === 'template'\n  )\n}\n\nexport function findDir(\n  node: ElementNode,\n  name: string | RegExp,\n  allowEmpty: boolean = false,\n): DirectiveNode | undefined {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (\n      p.type === NodeTypes.DIRECTIVE &&\n      (allowEmpty || p.exp) &&\n      (typeof name === 'string' ? p.name === name : name.test(p.name))\n    ) {\n      return p\n    }\n  }\n}\n```\n\n`isTemplateNode` 判断是否是 `<template>` 标签，`findDir` 查找指定名称的指令．\n\n## 实现 buildSlots\n\n在 `transforms/vSlot.ts` 中实现处理插槽插入的 `buildSlots` 函数．\n\n```ts\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type Property,\n  type SlotsExpression,\n  type TemplateChildNode,\n  createCallExpression,\n  createFunctionExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from '../ast'\nimport { WITH_CTX } from '../runtimeHelpers'\nimport type { TransformContext } from '../transform'\nimport { findDir, isStaticExp, isTemplateNode } from '../utils'\n\n// Build slots object for a component\nexport function buildSlots(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  slots: SlotsExpression\n} {\n  const { children } = node\n  const slotsProperties: Property[] = []\n\n  // 1. Check for slot with slotProps on component itself.\n  //    <Comp v-slot=\"{ prop }\"/>\n  const onComponentSlot = findDir(node, 'slot', true)\n  if (onComponentSlot) {\n    const { arg, exp } = onComponentSlot\n    slotsProperties.push(\n      createObjectProperty(\n        arg || createSimpleExpression('default', true),\n        buildSlotFn(exp, children, node.loc, context),\n      ),\n    )\n  }\n\n  // 2. Iterate through children and check for template slots\n  //    <template v-slot:foo=\"{ prop }\">\n  let hasTemplateSlots = false\n  const implicitDefaultChildren: TemplateChildNode[] = []\n\n  for (let i = 0; i < children.length; i++) {\n    const slotElement = children[i]\n    let slotDir: DirectiveNode | undefined\n\n    if (\n      !isTemplateNode(slotElement) ||\n      !(slotDir = findDir(slotElement, 'slot', true))\n    ) {\n      // not a <template v-slot>, skip.\n      if (slotElement.type !== NodeTypes.COMMENT) {\n        implicitDefaultChildren.push(slotElement)\n      }\n      continue\n    }\n\n    hasTemplateSlots = true\n    const { children: slotChildren, loc: slotLoc } = slotElement\n    const {\n      arg: slotName = createSimpleExpression(`default`, true),\n      exp: slotProps,\n    } = slotDir\n\n    const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc, context)\n    slotsProperties.push(createObjectProperty(slotName, slotFunction))\n  }\n\n  if (!onComponentSlot) {\n    if (!hasTemplateSlots) {\n      // implicit default slot (on component)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, children, node.loc, context),\n        ),\n      )\n    } else if (implicitDefaultChildren.length) {\n      // implicit default slot (mixed with named slots)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, implicitDefaultChildren, node.loc, context),\n        ),\n      )\n    }\n  }\n\n  const slots = createObjectExpression(\n    slotsProperties,\n    node.loc,\n  ) as SlotsExpression\n\n  return {\n    slots,\n  }\n}\n\nfunction buildSlotFn(\n  props: ExpressionNode | undefined,\n  children: TemplateChildNode[],\n  loc: any,\n  context: TransformContext,\n) {\n  const fn = createFunctionExpression(\n    props,\n    children,\n    false /* newline */,\n    children.length ? children[0].loc : loc,\n  )\n  fn.isSlot = true\n  return createCallExpression(context.helper(WITH_CTX), [fn], loc)\n}\n```\n\n`buildSlots` 函数处理以下三种模式：\n\n1. **组件本身有 v-slot 的情况**（`<Comp v-slot=\"{ prop }\"/>`）\n2. **使用 template 标签定义具名插槽的情况**（`<template #foo>`）\n3. **隐式默认插槽**（没有具名插槽时的子元素）\n\n## 更新 transformElement\n\n最后，更新 `transformElement.ts`，使用 `buildSlots` 处理组件的子元素．\n\n```ts\nimport { buildSlots } from './vSlot'\n\n// ...\n\n// children\nif (node.children.length > 0) {\n  if (isComponent) {\n    // For components, build slots object // [!code ++]\n    const { slots } = buildSlots(node, context) // [!code ++]\n    vnodeChildren = slots as SlotsExpression // [!code ++]\n  } else if (node.children.length === 1) {\n    const child = node.children[0]\n    const type = child.type\n    const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n    if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n      vnodeChildren = child as TemplateTextChildNode\n    } else {\n      vnodeChildren = node.children\n    }\n  } else {\n    vnodeChildren = node.children\n  }\n}\n```\n\n这样，插槽插入端的编译就完成了．\\\n组件的子元素会自动转换为插槽对象，生成如下代码．\n\n```vue\n<Comp>\n  <template #header>\n    <h1>Header</h1>\n  </template>\n  <template #default>\n    <p>Content</p>\n  </template>\n</Comp>\n```\n\n↓\n\n```js\n_createVNode(_component_Comp, null, {\n  header: _withCtx(() => [_createVNode('h1', null, 'Header')]),\n  default: _withCtx(() => [_createVNode('p', null, 'Content')]),\n})\n```\n\n基本的插槽编译器实现现在已完成！\n\n到此为止的源代码：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/085_component_slot_insert)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/090-other-directives.md",
    "content": "# 其他指令\n\n到目前为止，我们已经实现了 v-bind，v-on，v-if，v-for，v-model 等主要指令．\\\n在本章中，我们将实现其余的内置指令．\n\n我们要实现的指令如下：\n\n- v-text\n- v-html\n- v-cloak\n- v-pre\n\n关于 v-show，由于它需要运行时指令机制，我们将在自定义指令章节中介绍．\\\n另外，v-once 和 v-memo 与优化相关，计划在 Web Application Essentials 的 Optimizations 章节中介绍．\n\n## v-text\n\n### 目标开发者接口\n\nv-text 是一个更新元素 textContent 的指令．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello!')\n    return { msg }\n  },\n}\n</script>\n\n<template>\n  <span v-text=\"msg\"></span>\n  <!-- 等同于下面的写法 -->\n  <span>{{ msg }}</span>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-text\n\n### 实现方针\n\nv-text 的实现非常简单．\\\n在编译时，只需将 v-text 指令转换为 `textContent` 属性的绑定即可．\n\n```html\n<span v-text=\"msg\"></span>\n```\n\n↓\n\n```ts\nh('span', { textContent: msg })\n```\n\n### 在 compiler-dom 中实现 transformer\n\n由于 v-text 是 DOM 特有的指令，我们在 compiler-dom 中实现它．\n\n创建 `packages/compiler-dom/src/transforms/vText.ts`．\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-text is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-text will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\n关键点如下：\n\n- 如果 exp 不存在则输出错误\n- 如果存在子元素则输出警告并清除子元素（因为 v-text 会覆盖子元素）\n- 将 exp 绑定为 `textContent` 属性\n\n然后在 `packages/compiler-dom/src/index.ts` 中注册 transformer．\n\n```ts\nimport { transformVText } from './transforms/vText'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText, // [!code ++]\n}\n```\n\n这样 v-text 的实现就完成了！\n\n## v-html\n\n### 目标开发者接口\n\nv-html 是一个更新元素 innerHTML 的指令．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const rawHtml = ref('<span style=\"color: red\">This should be red.</span>')\n    return { rawHtml }\n  },\n}\n</script>\n\n<template>\n  <p>Using v-html directive: <span v-html=\"rawHtml\"></span></p>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-html\n\n::: warning\n由于 v-html 直接操作 innerHTML，可能成为 XSS 漏洞的来源．\\\n请避免使用 v-html 显示不受信任的用户输入．\n:::\n\n### 实现方针\n\n与 v-text 类似，v-html 在编译时转换为 `innerHTML` 属性的绑定．\n\n```html\n<span v-html=\"rawHtml\"></span>\n```\n\n↓\n\n```ts\nh('span', { innerHTML: rawHtml })\n```\n\n### 在 compiler-dom 中实现 transformer\n\n创建 `packages/compiler-dom/src/transforms/vHtml.ts`．\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-html is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-html will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\n结构与 v-text 几乎相同．唯一的区别是使用 `innerHTML` 而不是 `textContent`．\n\n在 `packages/compiler-dom/src/index.ts` 中注册 transformer．\n\n```ts\nimport { transformVHtml } from './transforms/vHtml'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText,\n  html: transformVHtml, // [!code ++]\n}\n```\n\n这样 v-html 的实现也完成了！\n\n## v-cloak\n\n### 目标开发者接口\n\nv-cloak 是一个用于在组件挂载前隐藏元素的指令．\\\n它与 CSS 配合使用，防止用户看到未编译的模板语法（如 mustache）．\n\n```css\n[v-cloak] {\n  display: none;\n}\n```\n\n```text\n<div v-cloak>\n  ｛｛ message ｝｝\n</div>\n```\n\n挂载后，v-cloak 属性会自动移除．\n\nhttps://vuejs.org/api/built-in-directives.html#v-cloak\n\n### 实现方针\n\nv-cloak 的实现非常简单．\\\n只需在挂载时从元素中移除 v-cloak 属性即可．\n\n这是在运行时而不是编译器中处理的．\\\n具体来说，我们在 `renderer.ts` 的 `mountElement` 函数中添加处理．\n\n### 在运行时实现\n\n在 `packages/runtime-core/src/renderer.ts` 的 `mountElement` 函数中添加以下处理．\n\n```ts\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  let el: RendererElement\n  const { type, props, children, shapeFlag } = vnode\n\n  el = vnode.el = hostCreateElement(type as string)\n\n  // ... 现有处理 ...\n\n  // 移除 v-cloak // [!code ++]\n  if (props && 'v-cloak' in props) { // [!code ++]\n    delete (el as any)['v-cloak'] // [!code ++]\n    hostRemoveAttribute(el, 'v-cloak') // [!code ++]\n  } // [!code ++]\n\n  hostInsert(el, container, anchor)\n\n  // ... 现有处理 ...\n}\n```\n\n虽然可以使用现有的 `hostPatchProp` 来实现 `hostRemoveAttribute`，但让我们简单地将其添加到 `nodeOps` 中．\n\n添加到 `packages/runtime-dom/src/nodeOps.ts`．\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // ... 现有处理 ...\n  removeAttribute: (el, key) => {\n    el.removeAttribute(key)\n  },\n}\n```\n\n还需要添加到 `packages/runtime-core/src/renderer.ts` 的 `RendererOptions` 类型中．\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement,\n> {\n  // ... 现有处理 ...\n  removeAttribute(el: HostElement, key: string): void\n}\n```\n\n这样 v-cloak 的实现就完成了！\n\n## v-pre\n\n### 目标开发者接口\n\nv-pre 是一个跳过该元素及其所有子元素编译的指令．\\\n当你想要原样显示 mustache 语法时使用．\n\n```text\n<template>\n  <span v-pre>｛｛ this will not be compiled ｝｝</span>\n</template>\n```\n\n上面的模板将原样显示文本 `｛｛ this will not be compiled ｝｝`．\n\nhttps://vuejs.org/api/built-in-directives.html#v-pre\n\n### 实现方针\n\n与其他指令不同，v-pre 在解析器阶段处理．\\\n当检测到带有 v-pre 属性的元素时，跳过该元素及其子元素的指令和 mustache 语法解析．\n\n### 在解析器中实现\n\n在 `packages/compiler-core/src/parse.ts` 中添加 v-pre 处理．\n\n首先，在解析器上下文中添加 `inVPre` 标志．\n\n```ts\nexport interface ParserContext {\n  // ... 现有属性 ...\n  inVPre: boolean // [!code ++]\n}\n\nfunction createParserContext(content: string, options: ParserOptions): ParserContext {\n  return {\n    // ... 现有处理 ...\n    inVPre: false, // [!code ++]\n  }\n}\n```\n\n接下来，在解析元素时检查 v-pre 属性，如果存在则将 `inVPre` 设置为 true．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // Start tag\n  const element = parseTag(context, TagType.Start)\n\n  // 检查 v-pre // [!code ++]\n  const isPreBoundary = element.props.some( // [!code ++]\n    p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre' // [!code ++]\n  ) // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = true // [!code ++]\n  } // [!code ++]\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = children\n\n    // End tag\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End)\n    }\n  }\n\n  // v-pre 结束 // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = false // [!code ++]\n  } // [!code ++]\n\n  return element\n}\n```\n\n然后，在 `inVPre` 为 true 时跳过指令和 mustache 语法的解析．\n\n修改 `parseAttribute` 函数．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // ... 属性名解析 ...\n\n  // 在 v-pre 中不作为指令解析 // [!code ++]\n  if (context.inVPre) { // [!code ++]\n    return { // [!code ++]\n      type: NodeTypes.ATTRIBUTE, // [!code ++]\n      name, // [!code ++]\n      value: value && { // [!code ++]\n        type: NodeTypes.TEXT, // [!code ++]\n        content: value.content, // [!code ++]\n        loc: value.loc, // [!code ++]\n      }, // [!code ++]\n      loc, // [!code ++]\n    } // [!code ++]\n  } // [!code ++]\n\n  // 指令解析 ...\n}\n```\n\n同样修改 `parseChildren` 函数以跳过 mustache 语法解析．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (startsWith(s, context.options.delimiters[0])) {\n      // 在 v-pre 中跳过 mustache // [!code ++]\n      if (!context.inVPre) { // [!code ++]\n        node = parseInterpolation(context)\n      } // [!code ++]\n    } else if (s[0] === '<') {\n      // ... 元素解析 ...\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\n这样 v-pre 的实现就完成了！\n\n## 验证行为\n\n让我们验证实现的指令是否正常工作．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>｛｛ msg ｝｝ will not be compiled</span>\n  </div>\n</template>\n```\n\n运行正常吗？\\\n这样基本的内置指令实现就完成了！\n\nv-show 和自定义指令将在下一章介绍．\\\nv-once 和 v-memo 计划在优化章节中介绍．\n\n到此为止的源代码：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/090_other_directives)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/100-chore-compiler.md",
    "content": "# 编译器细节优化\n\n在本章中，我们将对模板编译器进行一些调整以提高其质量．\\\n主要涉及以下两个主题：\n\n1. **空白处理** - 删除和压缩不必要的空白\n2. **文本节点合并** - 高效合并相邻的文本节点\n\n这些是为了提高生成代码质量的优化，而不是可见的功能．\n\n## 空白处理\n\n### 问题\n\n在当前的实现中，模板中的所有空白都会被原样保留．\\\n考虑以下模板：\n\n```html\n<div>\n  <span>Hello</span>\n  <span>World</span>\n</div>\n```\n\n在当前实现中，`<div>` 和 `<span>` 之间的换行和缩进会作为文本节点被保留．\\\n这会生成不必要的节点，可能影响性能．\n\n### Vue.js 的方法\n\nVue.js 使用 `whitespace` 选项来控制空白的处理方式．\n\n```ts\ntype WhitespaceStrategy = 'preserve' | 'condense'\n```\n\n- **`'condense'`**（默认）：压缩连续的空白并删除不必要的空白\n- **`'preserve'`**：原样保留空白\n\n### condense 模式的行为\n\n在 condense 模式下，空白按照以下规则处理：\n\n1. **开头/结尾的纯空白文本节点** → 删除\n2. **包含换行的元素间空白** → 删除\n3. **连续的空白** → 压缩为单个空格\n4. **不包含换行的元素间空白** → 保留（压缩为单个空格）\n\n示例：\n\n```html\n<div>   <span/>    </div>\n<!-- 结果：只有 <span/> 作为子节点（周围的空格被删除） -->\n\n<div/>\n<div/>\n<div/>\n<!-- 结果：只有 3 个 div 元素（包含换行的空白被删除） -->\n\n<span>foo</span>  <span>bar</span>\n<!-- 结果：元素间的空格被保留（没有换行） -->\n```\n\n### 实现\n\n首先，在 `ParserOptions` 中添加 `whitespace` 选项．\n\n`packages/compiler-core/src/options.ts`：\n\n```ts\nexport interface ParserOptions {\n  // ... 现有选项 ...\n  whitespace?: 'preserve' | 'condense' // [!code ++]\n}\n```\n\n在 `packages/compiler-core/src/parse.ts` 中添加空白处理函数．\n\n```ts\nfunction isAllWhitespace(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (\n      c !== 0x20 && // 空格\n      c !== 0x09 && // 制表符\n      c !== 0x0a && // 换行\n      c !== 0x0c && // 换页\n      c !== 0x0d    // 回车\n    ) {\n      return false\n    }\n  }\n  return true\n}\n\nfunction hasNewlineChar(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (c === 0x0a || c === 0x0d) {\n      return true\n    }\n  }\n  return false\n}\n\nfunction condense(content: string): string {\n  let result = ''\n  let prevIsWhitespace = false\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    const isWhitespace =\n      c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d\n    if (isWhitespace) {\n      if (!prevIsWhitespace) {\n        result += ' '\n        prevIsWhitespace = true\n      }\n    } else {\n      result += content[i]\n      prevIsWhitespace = false\n    }\n  }\n  return result\n}\n\nfunction condenseWhitespace(\n  nodes: TemplateChildNode[],\n  context: ParserContext,\n): TemplateChildNode[] {\n  const shouldCondense = context.options.whitespace !== 'preserve'\n  let removedWhitespace = false\n\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i]\n    if (node.type === NodeTypes.TEXT) {\n      if (!context.inPre) {\n        if (isAllWhitespace(node.content)) {\n          const prev = nodes[i - 1]?.type\n          const next = nodes[i + 1]?.type\n          // 以下情况删除：\n          // - 开头或结尾的空白\n          // - (condense 模式) 注释之间的空白\n          // - (condense 模式) 注释和元素之间的空白\n          // - (condense 模式) 包含换行的元素间空白\n          if (\n            !prev ||\n            !next ||\n            (shouldCondense &&\n              ((prev === NodeTypes.COMMENT &&\n                (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||\n                (prev === NodeTypes.ELEMENT &&\n                  (next === NodeTypes.COMMENT ||\n                    (next === NodeTypes.ELEMENT &&\n                      hasNewlineChar(node.content))))))\n          ) {\n            removedWhitespace = true\n            nodes[i] = null as any\n          } else {\n            // 否则压缩为单个空格\n            node.content = ' '\n          }\n        } else if (shouldCondense) {\n          // condense 模式下压缩连续空白\n          node.content = condense(node.content)\n        }\n      }\n    }\n  }\n\n  return removedWhitespace ? nodes.filter(Boolean) : nodes\n}\n```\n\n然后在解析元素时调用此函数．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // ... 现有代码 ...\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = condenseWhitespace(children, context) // [!code ++]\n    // element.children = children // [!code --]\n\n    // ...\n  }\n\n  return element\n}\n```\n\n同样对根节点应用相同的处理．\n\n```ts\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {},\n): RootNode => {\n  const context = createParserContext(content, options)\n  const children = parseChildren(context, [])\n  return createRoot(condenseWhitespace(children, context)) // [!code ++]\n  // return createRoot(children) // [!code --]\n}\n```\n\n## 文本节点合并 (transformText)\n\n### 问题\n\n在当前实现中，文本节点和 mustache 语法（`{{ }}`）被作为单独的节点处理．\n\n```html\n<div>abc {{ d }} {{ e }}</div>\n```\n\n这个模板有以下子节点：\n- `TEXT`: \"abc \"\n- `INTERPOLATION`: d\n- `TEXT`: \" \"\n- `INTERPOLATION`: e\n\n在代码生成时单独处理这些节点效率不高．\n\n### Vue.js 的方法\n\nVue.js 使用名为 `transformText` 的转换器将相邻的文本节点和 mustache 语法合并为一个 `CompoundExpression`．\n\n合并后：\n```ts\n// \"abc \" + d + \" \" + e\ncreateCompoundExpression(['abc ', d, ' ', e])\n```\n\n这允许在代码生成时输出高效的连接操作．\n\n### 实现\n\n创建 `packages/compiler-core/src/transforms/transformText.ts`．\n\n```ts\nimport type { NodeTransform } from '../transform'\nimport {\n  type CompoundExpressionNode,\n  ElementTypes,\n  NodeTypes,\n  createCallExpression,\n  createCompoundExpression,\n} from '../ast'\nimport { isText } from '../utils'\nimport { CREATE_TEXT } from '../runtimeHelpers'\nimport { PatchFlags } from '@chibivue/shared'\n\n// 将相邻的文本节点和 mustache 合并为单个表达式\n// 例如：<div>abc {{ d }} {{ e }}</div> 应该只有一个子节点\nexport const transformText: NodeTransform = (node, context) => {\n  if (\n    node.type === NodeTypes.ROOT ||\n    node.type === NodeTypes.ELEMENT ||\n    node.type === NodeTypes.FOR ||\n    node.type === NodeTypes.IF_BRANCH\n  ) {\n    // 在子节点处理完成后执行\n    return () => {\n      const children = node.children\n      let currentContainer: CompoundExpressionNode | undefined = undefined\n      let hasText = false\n\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child)) {\n          hasText = true\n          for (let j = i + 1; j < children.length; j++) {\n            const next = children[j]\n            if (isText(next)) {\n              if (!currentContainer) {\n                currentContainer = children[i] = createCompoundExpression(\n                  [child],\n                  child.loc,\n                )\n              }\n              // 合并相邻的文本节点\n              currentContainer.children.push(` + `, next)\n              children.splice(j, 1)\n              j--\n            } else {\n              currentContainer = undefined\n              break\n            }\n          }\n        }\n      }\n\n      if (\n        !hasText ||\n        // 对于只有单个文本子节点的普通元素，保持原样\n        // 运行时有直接设置 textContent 的优化路径\n        (children.length === 1 &&\n          (node.type === NodeTypes.ROOT ||\n            (node.type === NodeTypes.ELEMENT &&\n              node.tagType === ElementTypes.ELEMENT &&\n              !node.props.find(\n                p =>\n                  p.type === NodeTypes.DIRECTIVE &&\n                  !context.directiveTransforms[p.name],\n              ))))\n      ) {\n        return\n      }\n\n      // 将文本节点转换为 createTextVNode(text) 调用\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {\n          const callArgs: any[] = []\n          // createTextVNode 默认为单个空格，\n          // 所以单个空格时可以省略参数\n          if (child.type !== NodeTypes.TEXT || child.content !== ' ') {\n            callArgs.push(child)\n          }\n          // 为动态文本添加标志以在块内进行补丁\n          if (!context.ssr && !isStaticNode(child)) {\n            callArgs.push(PatchFlags.TEXT)\n          }\n          children[i] = {\n            type: NodeTypes.TEXT_CALL,\n            content: child,\n            loc: child.loc,\n            codegenNode: createCallExpression(\n              context.helper(CREATE_TEXT),\n              callArgs,\n            ),\n          }\n        }\n      }\n    }\n  }\n}\n\nfunction isStaticNode(node: any): boolean {\n  if (node.type === NodeTypes.TEXT) {\n    return true\n  }\n  if (node.type === NodeTypes.INTERPOLATION) {\n    return node.content.isStatic\n  }\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    return node.children.every((child: any) => {\n      if (typeof child === 'string') return true\n      return isStaticNode(child)\n    })\n  }\n  return false\n}\n```\n\n在 `packages/compiler-core/src/utils.ts` 中添加 `isText` 辅助函数．\n\n```ts\nexport function isText(\n  node: TemplateChildNode,\n): node is TextNode | InterpolationNode {\n  return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION\n}\n```\n\n在 `packages/compiler-core/src/ast.ts` 中添加 `TEXT_CALL` 节点类型和 `createCallExpression`．\n\n```ts\nexport const enum NodeTypes {\n  // ... 现有类型 ...\n  TEXT_CALL, // [!code ++]\n}\n\nexport interface TextCallNode extends Node {\n  type: NodeTypes.TEXT_CALL\n  content: TextNode | InterpolationNode | CompoundExpressionNode\n  codegenNode: CallExpression\n}\n\nexport function createCallExpression(\n  callee: string,\n  args: CallExpression['arguments'] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  }\n}\n```\n\n在 `packages/compiler-core/src/runtimeHelpers.ts` 中添加 `CREATE_TEXT`．\n\n```ts\nexport const CREATE_TEXT = Symbol('createTextVNode')\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ... 现有助手 ...\n  [CREATE_TEXT]: 'createTextVNode',\n}\n```\n\n### 注册转换器\n\n在 `packages/compiler-core/src/compile.ts` 中注册转换器．\n\n```ts\nimport { transformText } from './transforms/transformText'\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformElement,\n      transformSlotOutlet,\n      transformText, // [!code ++]\n    ],\n    {\n      on: transformOn,\n      bind: transformBind,\n      if: transformIf,\n      for: transformFor,\n      model: transformModel,\n    },\n  ]\n}\n```\n\n### 更新代码生成\n\n在 `packages/compiler-core/src/codegen.ts` 中添加 `TEXT_CALL` 节点处理．\n\n```ts\nfunction genNode(node: any, context: CodegenContext) {\n  switch (node.type) {\n    // ... 现有情况 ...\n    case NodeTypes.TEXT_CALL: // [!code ++]\n      genNode(node.codegenNode, context) // [!code ++]\n      break // [!code ++]\n  }\n}\n```\n\n### 更新运行时\n\n在 `packages/runtime-core/src/vnode.ts` 中添加 `createTextVNode`．\n\n```ts\nexport function createTextVNode(text: string = ' ', flag: number = 0): VNode {\n  return createVNode(Text, null, text, flag)\n}\n```\n\n从 `packages/runtime-core/src/index.ts` 导出．\n\n```ts\nexport { createTextVNode } from './vnode'\n```\n\n## 测试\n\n让我们用以下模板进行验证：\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const name = ref('World')\n    return { name }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello {{ name }}!</p>\n  </div>\n</template>\n```\n\n检查编译结果时，你应该看到：\n- 不必要的空白（换行和缩进）已被删除\n- `Hello `，`{{ name }}` 和 `!` 已被合并\n\n编译器的质量现在得到了提升！\n\n本章节的源代码：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/100_chore_compiler)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/110-parser-optimization.md",
    "content": "# 解析器优化\n\n::: info 关于本章\n本章介绍 Vue 3.4 中引入的新解析器架构．\\\n基于 htmlparser2 的状态机 tokenizer 使解析速度提高了 2 倍．\n:::\n\n## 背景\n\n在 Vue 3.4 中，模板编译器的内部实现进行了重大重构．到目前为止，我们在 chibivue 中实现的解析器是基于 Vue 3.3 及更早版本的架构．\n\n### 传统解析器（Vue 3.3 及更早版本）\n\n传统的 Vue 解析器是**递归下降解析器（recursive descent parser）**：\n\n```ts\n// 传统实现\nfunction parseChildren(context: ParserContext): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined\n\n    if (startsWith(s, '{{')) {\n      node = parseInterpolation(context)\n    } else if (s[0] === '<') {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context)\n      }\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\n这种方式的问题：\n- 大量使用**正则表达式**\n- 频繁的**前瞻（look-ahead）搜索**\n- 多次遍历模板字符串\n\n### 新解析器（Vue 3.4）\n\nVue 3.4 引入了基于 [htmlparser2](https://github.com/fb55/htmlparser2) 的**状态机 tokenizer**：\n\n```ts\n// 新实现\nconst enum State {\n  Text,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  BeforeAttrName,\n  InAttrName,\n  // ...\n}\n\nclass Tokenizer {\n  private state = State.Text\n  private index = 0\n\n  parse(input: string) {\n    for (let i = 0; i < input.length; i++) {\n      this.index = i\n      this.consume(input.charCodeAt(i))\n    }\n  }\n\n  private consume(char: number) {\n    switch (this.state) {\n      case State.Text:\n        this.handleText(char)\n        break\n      case State.BeforeTagName:\n        this.handleBeforeTagName(char)\n        break\n      // ...\n    }\n  }\n}\n```\n\n这种方式的优点：\n- **单次遍历**模板字符串\n- 不使用正则表达式（或最小化使用）\n- **逐字符**处理效率高\n- 清晰的**状态转换**提高可维护性\n\n<KawaikoNote variant=\"surprise\" title=\"速度提升 2 倍！\">\n\n这个状态机 tokenizer 实现了**一致的 2 倍**解析速度提升！\\\n令人惊讶的是，仅仅通过避免正则表达式和前瞻搜索，一个字符一个字符地顺序处理，就能实现如此显著的性能提升．\n\n</KawaikoNote>\n\n## 状态机 Tokenizer\n\n状态机 tokenizer 根据当前状态决定如何处理下一个字符．\n\n### 状态定义\n\n```ts\nconst enum State {\n  // 文本\n  Text = 1,\n\n  // 插值（Mustache）\n  InterpolationOpen,     // 检测 {{\n  Interpolation,         // {{ 内的内容\n  InterpolationClose,    // 检测 }}\n\n  // 标签\n  BeforeTagName,         // < 之后\n  InTagName,             // 标签名内部\n  InSelfClosingTag,      // 检测 />\n\n  // 属性\n  BeforeAttrName,        // 属性名之前\n  InAttrName,            // 属性名内部\n  AfterAttrName,         // 属性名之后（= 之前）\n  BeforeAttrValue,       // 属性值之前\n  InAttrValueDq,         // 双引号内的属性值\n  InAttrValueSq,         // 单引号内的属性值\n  InAttrValueNq,         // 无引号的属性值\n\n  // 指令\n  InDirName,             // 指令名（v-xxx）\n  InDirArg,              // 指令参数（:xxx）\n  InDirDynamicArg,       // 动态参数（[xxx]）\n  InDirModifier,         // 修饰符（.xxx）\n}\n```\n\n### 状态转换示例\n\n```\n<div v-if=\"show\">Hello {{ name }}</div>\n```\n\n此示例的状态转换：\n\n```\n< → BeforeTagName\nd → InTagName\ni → InTagName\nv → InTagName\n(空格) → BeforeAttrName\nv → InAttrName (或 InDirName)\n- → InDirName\ni → InDirName\nf → InDirName\n= → BeforeAttrValue\n\" → InAttrValueDq\ns → InAttrValueDq\nh → InAttrValueDq\no → InAttrValueDq\nw → InAttrValueDq\n\" → BeforeAttrName\n> → Text\nH → Text\n...\n{ → InterpolationOpen\n{ → Interpolation\n(空格) → Interpolation\nn → Interpolation\na → Interpolation\nm → Interpolation\ne → Interpolation\n(空格) → Interpolation\n} → InterpolationClose\n} → Text\n...\n```\n\n## Visitor 模式\n\n新解析器使用 **Visitor 模式**将 tokenizer 与 AST 构建分离．\n\n### Callbacks 接口\n\n```ts\ninterface Callbacks {\n  onText(start: number, end: number): void\n  onInterpolation(start: number, end: number): void\n  onOpenTag(tag: string, start: number): void\n  onCloseTag(tag: string, start: number, end: number): void\n  onSelfClosingTag(tag: string, start: number, end: number): void\n  onAttr(name: string, value: string | undefined, start: number, end: number): void\n  onDirective(\n    name: string,\n    arg: string | undefined,\n    modifiers: string[],\n    value: string | undefined,\n    start: number,\n    end: number\n  ): void\n  onComment(start: number, end: number): void\n}\n```\n\n### Tokenizer 与 Parser 的分离\n\n```ts\nclass Tokenizer {\n  private cbs: Callbacks\n\n  constructor(callbacks: Callbacks) {\n    this.cbs = callbacks\n  }\n\n  // Tokenizer 发出事件\n  private emitOpenTag(tag: string, start: number) {\n    this.cbs.onOpenTag(tag, start)\n  }\n\n  private emitText(start: number, end: number) {\n    this.cbs.onText(start, end)\n  }\n}\n\n// Parser 实现 Callbacks 来构建 AST\nclass Parser implements Callbacks {\n  private stack: ElementNode[] = []\n  private root: RootNode\n\n  onOpenTag(tag: string, start: number) {\n    const element: ElementNode = {\n      type: NodeTypes.ELEMENT,\n      tag,\n      children: [],\n      // ...\n    }\n    this.stack.push(element)\n  }\n\n  onCloseTag(tag: string, start: number, end: number) {\n    const element = this.stack.pop()!\n    const parent = this.stack[this.stack.length - 1]\n    if (parent) {\n      parent.children.push(element)\n    } else {\n      this.root.children.push(element)\n    }\n  }\n\n  onText(start: number, end: number) {\n    const parent = this.stack[this.stack.length - 1]\n    const text: TextNode = {\n      type: NodeTypes.TEXT,\n      content: this.source.slice(start, end),\n      // ...\n    }\n    parent.children.push(text)\n  }\n}\n```\n\n### 优点\n\n1. **关注点分离**：Tokenizer 只专注于字符解析，Parser 只专注于 AST 构建\n2. **可测试性**：每个组件可以独立测试\n3. **可复用性**：Tokenizer 可以重用于其他目的（语法高亮，Lint 等）\n4. **性能**：不生成不必要的中间数据结构\n\n<KawaikoNote variant=\"question\" title=\"什么是 Visitor 模式？\">\n\nVisitor 模式是一种\"将数据结构与其处理分离\"的设计模式．\\\nTokenizer \"只读取模板并发出事件\"，Parser \"只接收事件并构建 AST\"——简单的职责划分．\\\n这使得代码更容易理解和测试！\n\n</KawaikoNote>\n\n## 性能比较\n\n根据 Vue 3.4 博客文章：\n\n| 模板大小 | 改进率 |\n|---------|-------|\n| 小型 | ~2x |\n| 中型 | ~2x |\n| 大型 | ~2x |\n\n实现了一致的 2 倍加速．\n\n此改进惠及整个生态系统：\n- **Volar**：IDE 补全和类型检查\n- **vue-tsc**：类型检查\n- **构建工具**：Vite，Webpack 等\n- **社区插件**：ESLint，Prettier 等\n\n## chibivue 中的实现\n\n::: warning\n当前 chibivue 使用传统的递归下降解析器．\\\n迁移到 Vue 3.4 风格的 tokenizer 正在考虑作为未来的工作．\n:::\n\n基本实现概要：\n\n<KawaikoNote variant=\"base\" title=\"有兴趣就来挑战！\">\n\n本章介绍的状态机 tokenizer 在 chibivue 中还没有实现，但如果你有兴趣，可以尝试自己实现！\\\n参考 Vue 3.4 的源代码和 htmlparser2 会加深你的理解．\\\n解析器优化是框架开发中非常重要的技能．\n\n</KawaikoNote>\n\n```ts\n// packages/compiler-core/tokenizer.ts\nconst enum State {\n  Text = 1,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  // ...\n}\n\nconst enum CharCodes {\n  Lt = 0x3c,      // <\n  Gt = 0x3e,      // >\n  Slash = 0x2f,   // /\n  Eq = 0x3d,      // =\n  OpenBrace = 0x7b,  // {\n  CloseBrace = 0x7d, // }\n  // ...\n}\n\nexport class Tokenizer {\n  private state = State.Text\n  private buffer = ''\n  private sectionStart = 0\n  private index = 0\n\n  constructor(private cbs: Callbacks) {}\n\n  parse(input: string) {\n    this.buffer = input\n    while (this.index < input.length) {\n      const c = input.charCodeAt(this.index)\n      switch (this.state) {\n        case State.Text:\n          this.stateText(c)\n          break\n        case State.InterpolationOpen:\n          this.stateInterpolationOpen(c)\n          break\n        // ...\n      }\n      this.index++\n    }\n    this.finish()\n  }\n\n  private stateText(c: number) {\n    if (c === CharCodes.Lt) {\n      if (this.index > this.sectionStart) {\n        this.cbs.onText(this.sectionStart, this.index)\n      }\n      this.state = State.BeforeTagName\n      this.sectionStart = this.index\n    } else if (c === CharCodes.OpenBrace) {\n      this.state = State.InterpolationOpen\n    }\n  }\n\n  private stateInterpolationOpen(c: number) {\n    if (c === CharCodes.OpenBrace) {\n      if (this.index > this.sectionStart + 1) {\n        this.cbs.onText(this.sectionStart, this.index - 1)\n      }\n      this.state = State.Interpolation\n      this.sectionStart = this.index + 1\n    } else {\n      this.state = State.Text\n    }\n  }\n\n  // ...\n}\n```\n\n## 总结\n\n- Vue 3.4 引入了基于 htmlparser2 的状态机 tokenizer\n- 通过只扫描模板字符串一次，解析速度提高了 2 倍\n- Visitor 模式分离了 tokenizer 和 AST 构建，提高了可维护性\n- 此优化惠及整个生态系统（Volar，vue-tsc 等）\n\n## 参考链接\n\n- [Announcing Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) - Vue 官方博客\n- [htmlparser2](https://github.com/fb55/htmlparser2) - Tokenizer 基于的库\n- [Vue 3.4 Parser Refactor](https://github.com/vuejs/core/pull/9674) - GitHub PR\n"
  },
  {
    "path": "book/online-book/src/zh-cn/50-basic-template-compiler/500-custom-directive.md",
    "content": "# 自定义指令\n\n::: info 关于本章\n本章实现 Vue 的自定义指令功能．\\\n您将学习如何定义像 `v-focus` 这样的自定义指令，并对元素执行直接操作．\n:::\n\n## 什么是自定义指令？\n\nVue 的自定义指令是用于对 DOM 元素执行低级操作的功能．当需要进行组件抽象无法处理的直接 DOM 操作时使用．\n\n典型用例：\n\n- 元素自动聚焦（`v-focus`）\n- 点击外部检测（`v-click-outside`）\n- 元素延迟加载（`v-lazy`）\n- 工具提示显示（`v-tooltip`）\n\n```vue\n<script setup>\n// 定义自定义指令\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n</script>\n\n<template>\n  <input v-focus />\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"说实话很少用\">\n\n自定义指令用于\"想直接操作 DOM\"的场景，但说实话很少被使用．\\\n由于 Vapor Mode 的实现变更以及与静态分析的兼容性差，**能不用就不用**．\\\n尽量用组件处理能用组件处理的事情！\n\n</KawaikoNote>\n\n## 指令生命周期\n\n指令有类似于组件的生命周期钩子：\n\n```ts\nconst myDirective = {\n  // 在元素的属性或事件监听器应用之前\n  created(el, binding, vnode, prevVnode) {},\n\n  // 在元素插入 DOM 之前\n  beforeMount(el, binding, vnode, prevVnode) {},\n\n  // 在元素插入 DOM 之后\n  mounted(el, binding, vnode, prevVnode) {},\n\n  // 在父组件更新之前\n  beforeUpdate(el, binding, vnode, prevVnode) {},\n\n  // 在父组件和子组件更新之后\n  updated(el, binding, vnode, prevVnode) {},\n\n  // 在父组件卸载之前\n  beforeUnmount(el, binding, vnode, prevVnode) {},\n\n  // 在父组件卸载之后\n  unmounted(el, binding, vnode, prevVnode) {},\n}\n```\n\n每个钩子接收以下参数：\n\n- `el`：指令绑定的元素\n- `binding`：传递给指令的信息（值，参数等）\n- `vnode`：对应 el 的 VNode\n- `prevVnode`：更新前的 VNode（仅 beforeUpdate，updated）\n\n## 实现概述\n\n自定义指令的实现由三部分组成：\n\n1. **运行时端**：指令类型定义和 `withDirectives` 辅助函数\n2. **渲染器端**：在每个生命周期调用钩子\n3. **编译器端**：从模板生成 `withDirectives`\n\n## 运行时实现\n\n### 指令类型定义\n\n首先，定义指令类型：\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentPublicInstance | null\n  value: V\n  oldValue: V | null\n  arg?: string\n  dir: ObjectDirective<any>\n}\n\nexport type DirectiveHook<T = any> = (\n  el: T,\n  binding: DirectiveBinding,\n  vnode: VNode,\n  prevVNode: VNode | null\n) => void\n\nexport interface ObjectDirective<T = any> {\n  created?: DirectiveHook<T>\n  beforeMount?: DirectiveHook<T>\n  mounted?: DirectiveHook<T>\n  beforeUpdate?: DirectiveHook<T>\n  updated?: DirectiveHook<T>\n  beforeUnmount?: DirectiveHook<T>\n  unmounted?: DirectiveHook<T>\n}\n```\n\n### withDirectives 辅助函数\n\n编译器生成将带有指令的元素用 `withDirectives` 包装的代码：\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport type DirectiveArguments = Array<\n  | [ObjectDirective | undefined]\n  | [ObjectDirective | undefined, any]\n  | [ObjectDirective | undefined, any, string]\n>\n\nexport function withDirectives<T extends VNode>(\n  vnode: T,\n  directives: DirectiveArguments\n): T {\n  const internalInstance = currentRenderingInstance\n  if (internalInstance === null) return vnode\n\n  const instance = internalInstance.proxy\n\n  const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])\n  for (let i = 0; i < directives.length; i++) {\n    let [dir, value, arg] = directives[i]\n    if (dir) {\n      // 将函数形式的指令转换为对象形式\n      if (isFunction(dir)) {\n        dir = {\n          mounted: dir,\n          updated: dir,\n        } as ObjectDirective\n      }\n      bindings.push({\n        dir,\n        instance,\n        value,\n        oldValue: void 0,\n        arg,\n      })\n    }\n  }\n  return vnode\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"简单！\">\n\n`withDirectives` 只是给 VNode 添加 `dirs` 属性．\\\n实际的钩子调用由渲染器完成，所以这个实现只是简单地将信息附加到 VNode 上！\n\n</KawaikoNote>\n\n### 调用指令钩子\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport function invokeDirectiveHook(\n  vnode: VNode,\n  prevVNode: VNode | null,\n  name: keyof ObjectDirective\n): void {\n  const bindings = vnode.dirs!\n  const oldBindings = prevVNode && prevVNode.dirs!\n\n  for (let i = 0; i < bindings.length; i++) {\n    const binding = bindings[i]\n    // 更新时设置旧值\n    if (oldBindings) {\n      binding.oldValue = oldBindings[i].value\n    }\n\n    const hook = binding.dir[name] as DirectiveHook | undefined\n    if (hook) {\n      hook(vnode.el, binding, vnode, prevVNode)\n    }\n  }\n}\n```\n\n## 渲染器实现\n\n渲染器在元素挂载和更新的各个时机调用 `invokeDirectiveHook`：\n\n```ts\n// packages/runtime-core/src/renderer.ts\n\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const { type, props, children, dirs } = vnode\n\n  const el = (vnode.el = hostCreateElement(type as string))\n\n  // 挂载子元素\n  if (typeof children === 'string') {\n    hostSetElementText(el, children)\n  } else if (isArray(children)) {\n    mountChildren(children as VNodeArrayChildren, el, null, parentComponent)\n  }\n\n  // 指令：created 钩子\n  dirs && invokeDirectiveHook(vnode, null, 'created')\n\n  // 设置 props\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, null, props[key])\n    }\n  }\n\n  // 指令：beforeMount 钩子\n  dirs && invokeDirectiveHook(vnode, null, 'beforeMount')\n\n  // 插入 DOM\n  hostInsert(el, container, anchor!)\n\n  // 指令：mounted 钩子\n  dirs && invokeDirectiveHook(vnode, null, 'mounted')\n}\n\nconst patchElement = (\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const el = (n2.el = n1.el!)\n  const { dirs } = n2\n  const oldProps = n1.props ?? {}\n  const newProps = n2.props ?? {}\n\n  // 指令：beforeUpdate 钩子\n  dirs && invokeDirectiveHook(n2, n1, 'beforeUpdate')\n\n  // 更新子元素和 props\n  patchChildren(n1, n2, el, null, parentComponent)\n  patchProps(el, oldProps, newProps)\n\n  // 指令：updated 钩子\n  dirs && invokeDirectiveHook(n2, n1, 'updated')\n}\n```\n\n## 向 VNode 添加 dirs 属性\n\n向 VNode 类型定义添加 `dirs`：\n\n```ts\n// packages/runtime-core/src/vnode.ts\n\nexport interface VNode<ExtraProps = { [key: string]: any }> {\n  type: VNodeTypes\n  props: (VNodeProps & ExtraProps) | null\n  children: VNodeNormalizedChildren\n  el: RendererNode | null\n  key: string | number | symbol | null\n  ref: Ref | null\n  shapeFlag: number\n  dirs?: DirectiveBinding[] | null  // 添加\n}\n```\n\n## 编译器实现\n\n### 注册 WITH_DIRECTIVES 辅助函数\n\n```ts\n// packages/compiler-core/src/runtimeHelpers.ts\n\nexport const WITH_DIRECTIVES: unique symbol = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_DIRECTIVES]: 'withDirectives',\n}\n```\n\n### 代码生成\n\n当 VNode 有指令时，用 `withDirectives` 包装：\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper } = context\n  const { tag, props, children, directives } = node\n\n  // 如果有指令，用 withDirectives 包装\n  if (directives) {\n    push(helper(WITH_DIRECTIVES) + `(`)\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`, node)\n  genNodeList(genNullableArgs([tag, props, children]), context)\n  push(`)`)\n\n  if (directives) {\n    push(`, `)\n    genNode(directives, context)\n    push(`)`)\n  }\n}\n```\n\n生成代码示例：\n\n```ts\n// 模板：<input v-focus />\n\n// 生成的代码\nwithDirectives(\n  createElementVNode('input'),\n  [[vFocus]]\n)\n\n// 模板：<div v-my-directive:arg.modifier=\"value\" />\n\n// 生成的代码\nwithDirectives(\n  createElementVNode('div'),\n  [[vMyDirective, value, 'arg', { modifier: true }]]\n)\n```\n\n## 测试\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\n\n// v-focus 指令\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n\n// v-color 指令\nconst vColor = {\n  mounted(el, binding) {\n    el.style.color = binding.value\n  },\n  updated(el, binding) {\n    el.style.color = binding.value\n  }\n}\n\nconst color = ref('red')\n</script>\n\n<template>\n  <input v-focus placeholder=\"自动聚焦\" />\n\n  <p v-color=\"color\">这段文字是 {{ color }} 色</p>\n\n  <button @click=\"color = 'blue'\">变蓝</button>\n  <button @click=\"color = 'green'\">变绿</button>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"实现完成！\">\n\n自定义指令的实现完成了！\\\n通过运行时，渲染器和编译器的协同工作，现在可以使用像 `v-focus` 这样的自定义指令了．\\\nv-model 内部也是作为指令实现的，请务必查看！\n\n</KawaikoNote>\n\n## 总结\n\n- 自定义指令是用于直接 DOM 操作的低级 API\n- `withDirectives` 将指令信息附加到 VNode\n- 渲染器在每个生命周期调用钩子\n- 编译器从模板生成 `withDirectives`\n\n## 参考链接\n\n- [Vue.js - 自定义指令](https://cn.vuejs.org/guide/reusability/custom-directives.html) - Vue 官方文档\n"
  },
  {
    "path": "book/online-book/src/zh-cn/60-basic-sfc-compiler/010-script-setup.md",
    "content": "# 支持 script setup\n\n::: info 关于本章\n本章介绍如何实现 Vue 3 的 `<script setup>` 语法．\\\n学习 script setup 的工作原理，以更简洁的方式编写组件．\n:::\n\n## 什么是 script setup？\n\n`<script setup>` 是 Vue 3.2 引入的编译时语法糖．与传统的 Options API 或 Composition API 相比，它可以更简洁地编写组件．\n\n```vue\n<!-- 传统写法 -->\n<script>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: { MyComponent },\n  setup() {\n    const count = ref(0)\n    const increment = () => count.value++\n    return { count, increment }\n  }\n}\n</script>\n\n<!-- script setup 写法 -->\n<script setup>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n```\n\n<KawaikoNote variant=\"surprise\" title=\"简洁多了！\">\n\n使用 script setup，不需要 `export default` 或 `return`，导入的组件也会自动注册．\\\n代码变得非常干净！\n\n</KawaikoNote>\n\n## 实现概述\n\nscript setup 的编译包含以下步骤：\n\n1. **导入分析和提升**：提取 import 语句并移动到文件顶部\n2. **绑定分析**：跟踪变量声明和函数定义\n3. **宏处理**：处理 defineProps，defineEmits 等（后续章节介绍）\n4. **代码转换**：转换为 setup 函数并生成 return 语句\n\n## compileScript 函数\n\n`compileScript` 函数是编译 SFC 脚本部分的核心函数．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nexport function compileScript(\n  sfc: SFCDescriptor,\n  options: SFCScriptCompileOptions,\n): SFCScriptBlock {\n  let { script, scriptSetup, source } = sfc\n\n  // 使用 Babel 解析\n  const scriptAst = _parse(script?.content ?? \"\", { sourceType: \"module\" }).program\n  const scriptSetupAst = _parse(scriptSetup?.content ?? \"\", { sourceType: \"module\" }).program\n\n  // 没有 script setup 时使用传统处理\n  if (!scriptSetup) {\n    if (!script) {\n      throw new Error(`SFC contains no <script> tags.`)\n    }\n    return { ...script, bindings: analyzeScriptBindings(scriptAst.body) }\n  }\n\n  // 初始化元数据\n  const bindingMetadata: BindingMetadata = {}\n  const userImports: Record<string, ImportBinding> = Object.create(null)\n  const setupBindings: Record<string, BindingTypes> = Object.create(null)\n\n  const s = new MagicString(source)\n  // ... 转换处理\n}\n```\n\n## 导入提升\n\nscript setup 内的 import 语句需要移动（提升）到生成代码的开头．\n\n```ts\n// 1.2 walk import declarations of <script setup>\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ImportDeclaration\") {\n    // 将导入移动到文件顶部\n    hoistNode(node)\n\n    // 移除重复导入\n    for (let i = 0; i < node.specifiers.length; i++) {\n      const specifier = node.specifiers[i]\n      const local = specifier.local.name\n      const imported = getImportedName(specifier)\n      const source = node.source.value\n\n      const existing = userImports[local]\n      if (existing) {\n        if (existing.source === source && existing.imported === imported) {\n          removeSpecifier(i)\n        }\n      } else {\n        registerUserImport(source, local, imported, true)\n      }\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"question\" title=\"为什么需要提升？\">\n\n在生成的代码中，import 语句需要放在 `setup()` 函数外部．\\\n提升将 `<script setup>` 内的导入移动到正确的位置．\n\n顺便说一下，`<script setup>` 内的 `export` 会报错．\\\n但是 `export type` 是可以的，因为它只是类型信息！\n\n</KawaikoNote>\n\n## 绑定分析\n\n为了正确解析模板中引用的变量，我们需要分析脚本中的绑定．\n\n```ts\nfunction walkDeclaration(\n  node: Declaration,\n  bindings: Record<string, BindingTypes>,\n  userImportAliases: Record<string, string> = {},\n) {\n  if (node.type === \"VariableDeclaration\") {\n    const isConst = node.kind === \"const\"\n\n    for (const { id, init } of node.declarations) {\n      if (id.type === \"Identifier\") {\n        let bindingType\n        if (isConst && isStaticNode(init!)) {\n          bindingType = BindingTypes.LITERAL_CONST\n        } else if (isCallOf(init, userImportAliases[\"reactive\"])) {\n          bindingType = BindingTypes.SETUP_REACTIVE_CONST\n        } else if (isCallOf(init, userImportAliases[\"ref\"])) {\n          bindingType = BindingTypes.SETUP_REF\n        } else if (isConst) {\n          bindingType = BindingTypes.SETUP_MAYBE_REF\n        } else {\n          bindingType = BindingTypes.SETUP_LET\n        }\n        registerBinding(bindings, id, bindingType)\n      }\n    }\n  } else if (node.type === \"FunctionDeclaration\") {\n    bindings[node.id!.name] = BindingTypes.SETUP_CONST\n  }\n}\n```\n\n绑定类型决定了变量在模板中的引用方式：\n\n| 类型 | 描述 | 模板引用 |\n|------|------|---------|\n| `SETUP_REF` | 用 ref() 创建 | 自动添加 `.value` |\n| `SETUP_REACTIVE_CONST` | 用 reactive() 创建 | 直接引用 |\n| `SETUP_CONST` | 常量 | 直接引用 |\n| `SETUP_LET` | let/var 变量 | 直接引用 |\n\n## 内联模板\n\n使用 script setup 时，模板可以内联到 setup 函数内部．\n\n```ts\n// 10. generate return statement\nlet returned\nif (options.inlineTemplate) {\n  if (sfc.template) {\n    const { code, preamble } = compileTemplate({\n      source: sfc.template.content.trim(),\n      compilerOptions: { inline: true, bindingMetadata },\n    })\n\n    if (preamble) {\n      s.prepend(preamble)\n    }\n    returned = code\n  } else {\n    returned = `() => {}`\n  }\n}\ns.appendRight(endOffset, `\\nreturn ${returned}\\n`)\n```\n\n生成代码示例：\n\n```ts\n// 输入\n// <script setup>\n// import { ref } from 'chibivue'\n// const count = ref(0)\n// </script>\n// <template>\n//   <p>{{ count }}</p>\n// </template>\n\n// 输出\nimport { ref } from 'chibivue'\n\nexport default {\n  setup(__props) {\n    const count = ref(0)\n\n    return (_ctx) => {\n      return h('p', count.value)\n    }\n  }\n}\n```\n\n## 与 Vite 插件集成\n\nVite 插件检测并编译 script setup．\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/script.ts\n\nexport function resolveScript(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n): SFCScriptBlock | null {\n  if (!descriptor.script && !descriptor.scriptSetup) return null\n\n  return options.compiler.compileScript(descriptor, {\n    inlineTemplate: isUseInlineTemplate(descriptor),\n  })\n}\n\nexport function isUseInlineTemplate(descriptor: SFCDescriptor): boolean {\n  return !!descriptor.scriptSetup\n}\n```\n\n## 测试\n\n```vue\n<script setup>\nimport { ref, computed } from 'chibivue'\n\nconst count = ref(0)\nconst double = computed(() => count.value * 2)\n\nconst increment = () => {\n  count.value++\n}\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ count }}</p>\n    <p>Double: {{ double }}</p>\n    <button @click=\"increment\">+1</button>\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"实现完成！\">\n\nscript setup 的基本实现完成了！\\\n与传统写法相比，现在可以更简洁地编写组件．\\\n下一章我们将学习如何实现 `defineProps` 和 `defineEmits` 宏．\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/010_script_setup)\n\n## 总结\n\n- `<script setup>` 是更简洁编写 Composition API 的语法糖\n- `compileScript` 处理核心转换逻辑\n- 导入提升和绑定分析是重要步骤\n- 模板被内联到 setup 函数内部\n\n## 参考链接\n\n- [Vue.js - script setup](https://cn.vuejs.org/api/sfc-script-setup.html) - Vue 官方文档\n- [RFC: script setup](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/zh-cn/60-basic-sfc-compiler/020-define-props.md",
    "content": "# 支持 defineProps\n\n::: info 关于本章\n本章介绍如何实现 `<script setup>` 中使用的 `defineProps` 宏．\\\n学习编译器宏的工作原理以及 props 声明的处理方式．\n:::\n\n## 什么是 defineProps？\n\n`defineProps` 是一个编译器宏，用于在 `<script setup>` 内声明组件的 props．\n\n```vue\n<script setup>\n// 运行时声明\nconst props = defineProps({\n  title: String,\n  count: {\n    type: Number,\n    default: 0\n  }\n})\n\nconsole.log(props.title)\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"什么是编译器宏？\">\n\n`defineProps` 不是普通函数．它是**编译器宏**．\\\n它在编译时会被特殊处理，在运行时会被擦除．\\\n这就是为什么不需要导入就可以使用！\n\n</KawaikoNote>\n\n## 实现概述\n\ndefineProps 的处理包含以下步骤：\n\n1. **检测宏调用**：在 AST 中找到 `defineProps()` 调用\n2. **提取参数**：获取 props 定义对象\n3. **删除代码**：删除原始的 `defineProps()` 调用\n4. **添加到选项**：作为 `props` 选项添加到输出\n5. **注册绑定**：将 props 注册为 `PROPS` 类型\n\n## processDefineProps 函数\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_PROPS = \"defineProps\"\n\nlet propsRuntimeDecl: Node | undefined\nlet propsIdentifier: string | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  // 保存参数（props 定义对象）\n  propsRuntimeDecl = node.arguments[0]\n\n  // 如果赋值给变量，保存标识符\n  // const props = defineProps(...) 中的 \"props\" 部分\n  if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST 遍历\n\n遍历 `<script setup>` 的主体来检测 `defineProps`．\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  // 表达式语句（单独调用 defineProps()）\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr)) {\n      // 删除宏调用\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  // 变量声明（const props = defineProps(...)）\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        if (processDefineProps(init, declId)) {\n          // 删除声明\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## 注册 Props 绑定\n\n作为 props 声明的变量会被注册到绑定元数据中，以便从模板中引用．\n\n```ts\n// 7. analyze binding metadata\nif (propsRuntimeDecl) {\n  for (const key of getObjectExpressionKeys(propsRuntimeDecl as ObjectExpression)) {\n    bindingMetadata[key] = BindingTypes.PROPS\n  }\n}\n```\n\n通过注册为 `BindingTypes.PROPS`，模板编译器可以正确处理对 props 的访问．\n\n## 处理 Props 标识符\n\n当赋值给变量如 `const props = defineProps(...)` 时，需要使该变量可访问．\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\nif (propsIdentifier) {\n  // 添加 const props = __props;\n  s.prependLeft(startOffset, `\\nconst ${propsIdentifier} = __props;\\n`)\n}\n```\n\n## 添加到选项\n\n最终，props 定义作为组件选项输出．\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  let declCode = scriptSetup.content\n    .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)\n    .trim()\n  runtimeOptions += `\\n  props: ${declCode},`\n}\n\ns.prependLeft(\n  startOffset,\n  `\\nexport default {\\n${runtimeOptions}\\nsetup(${args}) {\\n`\n)\n```\n\n## 转换结果示例\n\n```vue\n<!-- 输入 -->\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n\n<template>\n  <h1>{{ title }}</h1>\n</template>\n```\n\n```ts\n// 输出\nexport default {\n  props: {\n    title: String,\n    count: Number\n  },\n  setup(__props) {\n    const props = __props;\n\n    return (_ctx) => {\n      return h('h1', _ctx.title)\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"简单！\">\n\n`defineProps` 看起来复杂，但做的事情很简单：\n1. 将参数移动到 `props` 选项\n2. 删除 `defineProps()` 调用\n3. 如果有变量，替换为对 `__props` 的引用\n\n</KawaikoNote>\n\n## 测试\n\n```vue\n<script setup>\nimport { computed } from 'chibivue'\n\nconst props = defineProps({\n  firstName: String,\n  lastName: String\n})\n\nconst fullName = computed(() => `${props.firstName} ${props.lastName}`)\n</script>\n\n<template>\n  <div>\n    <p>First: {{ firstName }}</p>\n    <p>Last: {{ lastName }}</p>\n    <p>Full: {{ fullName }}</p>\n  </div>\n</template>\n```\n\n父组件：\n\n```vue\n<script setup>\nimport ChildComponent from './ChildComponent.vue'\n</script>\n\n<template>\n  <ChildComponent firstName=\"John\" lastName=\"Doe\" />\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"实现完成！\">\n\ndefineProps 的实现完成了！\\\n现在你理解了编译器宏的基本机制．\\\n下一章我们将学习如何实现 `defineEmits` 宏．\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/020_define_props)\n\n## 总结\n\n- `defineProps` 是编译器宏，在编译时处理\n- 遍历 AST 检测 `defineProps()` 调用\n- 参数转换为 `props` 选项，调用本身被删除\n- Props 注册为 `BindingTypes.PROPS` 以便模板访问\n\n## 参考链接\n\n- [Vue.js - defineProps](https://cn.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue 官方文档\n"
  },
  {
    "path": "book/online-book/src/zh-cn/60-basic-sfc-compiler/030-define-emits.md",
    "content": "# 支持 defineEmits\n\n::: info 关于本章\n本章介绍如何实现 `<script setup>` 中使用的 `defineEmits` 宏．\\\n学习子组件向父组件发送事件的机制．\n:::\n\n## 什么是 defineEmits？\n\n`defineEmits` 是一个编译器宏，用于在 `<script setup>` 内声明组件发出的事件．\n\n```vue\n<script setup>\nconst emit = defineEmits(['change', 'update'])\n\nfunction handleClick() {\n  emit('change', 'new value')\n}\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"与 defineProps 有什么区别？\">\n\n`defineProps` 用于从父组件向子组件传递数据，\\\n`defineEmits` 用于从子组件向父组件通知事件．\\\n记住它们是一对！\n\n</KawaikoNote>\n\n## 实现概述\n\ndefineEmits 的处理与 defineProps 非常相似：\n\n1. **检测宏调用**：在 AST 中找到 `defineEmits()` 调用\n2. **提取参数**：获取事件定义数组或对象\n3. **删除代码**：删除原始的 `defineEmits()` 调用\n4. **添加到选项**：作为 `emits` 选项添加到输出\n5. **提供 emit 函数**：从 setup 的上下文中获取 `emit`\n\n## processDefineEmits 函数\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_EMITS = \"defineEmits\"\n\nlet emitsRuntimeDecl: Node | undefined\nlet emitIdentifier: string | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  // 保存事件定义\n  emitsRuntimeDecl = node.arguments[0]\n\n  // 如果赋值给变量，保存标识符\n  // const emit = defineEmits(...) 中的 \"emit\" 部分\n  if (declId) {\n    emitIdentifier =\n      declId.type === \"Identifier\"\n        ? declId.name\n        : scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST 遍历\n\n与 defineProps 类似，遍历 `<script setup>` 的主体来检测 `defineEmits`．\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr) || processDefineEmits(expr)) {\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        const isDefineProps = processDefineProps(init, declId)\n        const isDefineEmits = processDefineEmits(init, declId)\n        if (isDefineProps || isDefineEmits) {\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## 设置 emit 函数\n\n从 `defineEmits` 获取的 emit 函数从 setup 函数的第二个参数（SetupContext）中获取．\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\n\nconst destructureElements: string[] = []\nif (emitIdentifier) {\n  destructureElements.push(\n    emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`\n  )\n}\n\nif (destructureElements.length) {\n  args += `, { ${destructureElements.join(\", \")} }`\n}\n```\n\n这会生成如下代码：\n\n```ts\n// 对于 const emit = defineEmits(['change'])\nsetup(__props, { emit }) {\n  // ...\n}\n\n// 对于 const emitFn = defineEmits(['change'])\nsetup(__props, { emit: emitFn }) {\n  // ...\n}\n```\n\n## 添加到选项\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  runtimeOptions += `\\n  props: ${...},`\n}\nif (emitsRuntimeDecl) {\n  runtimeOptions += `\\n  emits: ${scriptSetup.content\n    .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)\n    .trim()},`\n}\n```\n\n## 转换结果示例\n\n```vue\n<!-- 输入 -->\n<script setup>\nconst emit = defineEmits(['update', 'delete'])\n\nfunction handleUpdate(value) {\n  emit('update', value)\n}\n</script>\n\n<template>\n  <button @click=\"handleUpdate('new')\">Update</button>\n</template>\n```\n\n```ts\n// 输出\nexport default {\n  emits: ['update', 'delete'],\n  setup(__props, { emit }) {\n    function handleUpdate(value) {\n      emit('update', value)\n    }\n\n    return (_ctx) => {\n      return h('button', { onClick: _ctx.handleUpdate.bind(_ctx, 'new') }, 'Update')\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"与 defineProps 对称！\">\n\n`defineEmits` 的实现与 `defineProps` 几乎相同：\n1. 检测宏调用\n2. 将参数移动到 `emits` 选项\n3. 如果有变量，转换为从 SetupContext 获取\n\n容易记住！\n\n</KawaikoNote>\n\n## 测试\n\n子组件：\n\n```vue\n<script setup>\nconst props = defineProps({\n  modelValue: String\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nfunction updateValue(e) {\n  emit('update:modelValue', e.target.value)\n}\n</script>\n\n<template>\n  <input :value=\"modelValue\" @input=\"updateValue\" />\n</template>\n```\n\n父组件：\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\nimport CustomInput from './CustomInput.vue'\n\nconst text = ref('')\n</script>\n\n<template>\n  <CustomInput v-model=\"text\" />\n  <p>输入值: {{ text }}</p>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"实现完成！\">\n\ndefineEmits 的实现完成了！\\\n现在可以使用 props 和 emits 两个编译器宏了．\\\n下一章我们将学习如何实现 scoped CSS．\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/030_define_emits)\n\n## 总结\n\n- `defineEmits` 是声明子到父事件发送的宏\n- 处理模式与 `defineProps` 非常相似\n- emit 函数从 SetupContext 解构获取\n- 作为 `emits` 选项添加到组件\n\n## 参考链接\n\n- [Vue.js - defineEmits](https://cn.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue 官方文档\n"
  },
  {
    "path": "book/online-book/src/zh-cn/60-basic-sfc-compiler/040-scoped-css.md",
    "content": "# 支持 Scoped CSS\n\n::: info 关于本章\n本章介绍如何实现 Vue 的 Scoped CSS 功能．\\\n学习如何为每个组件隔离样式，防止样式冲突．\n:::\n\n## 什么是 Scoped CSS？\n\nScoped CSS 是将 `<style scoped>` 中定义的样式仅应用于该组件的功能．\n\n```vue\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\n此样式不会影响其他组件中具有相同类名的元素．\n\n<KawaikoNote variant=\"question\" title=\"为什么需要 Scoped CSS？\">\n\n在大型应用中，不同组件可能使用相同的类名．\\\n没有 Scoped CSS，样式可能会意外影响其他组件．\\\n通过为每个组件隔离样式，可以安全地进行样式设计！\n\n</KawaikoNote>\n\n## 工作原理\n\nScoped CSS 通过以下步骤实现：\n\n1. **生成作用域 ID**：为每个组件创建唯一 ID\n2. **转换模板**：为元素添加 `data-v-xxx` 属性\n3. **转换样式**：为选择器添加 `[data-v-xxx]`\n\n### 转换示例\n\n```vue\n<!-- 输入 -->\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\n```html\n<!-- 输出 (HTML) -->\n<p class=\"message\" data-v-7ba5bd90>Hello</p>\n\n<!-- 输出 (CSS) -->\n<style>\n.message[data-v-7ba5bd90] {\n  color: red;\n}\n</style>\n```\n\n## 生成作用域 ID\n\n为每个组件生成唯一 ID．通常使用文件路径的哈希值．\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nimport { createHash } from 'crypto'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  }\n\n  // 生成作用域 ID\n  descriptor.id = createHash('sha256')\n    .update(filename + source)\n    .digest('hex')\n    .slice(0, 8)\n\n  // ... 其余解析处理\n}\n```\n\n## 扩展 SFCStyleBlock\n\n为样式块添加 scoped 信息．\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\"\n  scoped?: boolean  // 添加\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  // ...\n  node.props.forEach((p) => {\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      attrs[p.name] = p.value ? p.value.content || true : true\n      if (type === \"style\") {\n        if (p.name === \"scoped\") {\n          (block as SFCStyleBlock).scoped = true\n        }\n      }\n    }\n  })\n  return block\n}\n```\n\n## 模板转换\n\n在模板编译期间为元素添加 scopeId 属性．\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper, scopeId } = context\n  const { tag, props, children } = node\n\n  // 如果存在 scopeId，添加到 props\n  let propsWithScope = props\n  if (scopeId) {\n    const scopeIdProp = `\"data-v-${scopeId}\": \"\"`\n    if (props) {\n      // 与现有 props 合并\n      propsWithScope = `{ ...${props}, ${scopeIdProp} }`\n    } else {\n      propsWithScope = `{ ${scopeIdProp} }`\n    }\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`)\n  genNodeList(genNullableArgs([tag, propsWithScope, children]), context)\n  push(`)`)\n}\n```\n\n## 样式转换\n\n为 CSS 选择器添加作用域属性选择器．\n\n```ts\n// packages/compiler-sfc/src/compileStyle.ts\n\nimport postcss from 'postcss'\n\nexport interface SFCStyleCompileOptions {\n  source: string\n  filename: string\n  id: string\n  scoped?: boolean\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): string {\n  const { source, id, scoped } = options\n\n  if (!scoped) {\n    return source\n  }\n\n  // 使用 PostCSS 转换选择器\n  const result = postcss([scopedPlugin(id)]).process(source, { from: undefined })\n  return result.css\n}\n\nfunction scopedPlugin(id: string) {\n  const scopeId = `data-v-${id}`\n\n  return {\n    postcssPlugin: 'vue-sfc-scoped',\n    Rule(rule) {\n      // 为选择器添加 [data-v-xxx]\n      rule.selectors = rule.selectors.map((selector) => {\n        return `${selector}[${scopeId}]`\n      })\n    },\n  }\n}\n```\n\n## Vite 插件集成\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/main.ts\n\nasync function genStyleCode(descriptor: SFCDescriptor): Promise<string> {\n  let stylesCode = ``\n\n  for (let i = 0; i < descriptor.styles.length; i++) {\n    const style = descriptor.styles[i]\n    const src = descriptor.filename\n    const scoped = style.scoped ? '&scoped=true' : ''\n    const query = `?chibivue&type=style&index=${i}${scoped}&lang.css`\n    const styleRequest = src + query\n    stylesCode += `\\nimport ${JSON.stringify(styleRequest)}`\n  }\n\n  return stylesCode\n}\n\n// 在 Vite 插件的 load 中编译样式\nload(id) {\n  const { filename, query } = parseChibiVueRequest(id)\n  if (query.chibivue && query.type === \"style\") {\n    const descriptor = getDescriptor(filename, options)!\n    const style = descriptor.styles[query.index!]\n\n    if (query.scoped) {\n      return {\n        code: compileStyle({\n          source: style.content,\n          filename,\n          id: descriptor.id,\n          scoped: true,\n        })\n      }\n    }\n\n    return { code: style.content }\n  }\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"PostCSS 的力量！\">\n\n我们使用 PostCSS 进行样式转换．\\\nPostCSS 是一个可以将 CSS 作为 AST 处理的工具，使选择器转换变得简单．\\\nVue.js 内部也使用 PostCSS！\n\n</KawaikoNote>\n\n## 测试\n\n```vue\n<!-- ComponentA.vue -->\n<template>\n  <p class=\"text\">Component A</p>\n</template>\n\n<style scoped>\n.text {\n  color: red;\n}\n</style>\n```\n\n```vue\n<!-- ComponentB.vue -->\n<template>\n  <p class=\"text\">Component B</p>\n</template>\n\n<style scoped>\n.text {\n  color: blue;\n}\n</style>\n```\n\n两个组件使用相同的类名 `.text`，但显示不同的颜色．\n\n## 特殊选择器\n\nScoped CSS 支持几个特殊的选择器．\n\n### :deep() 选择器\n\n用于修改子组件的样式．\n\n```vue\n<style scoped>\n:deep(.child-class) {\n  color: blue;\n}\n</style>\n```\n\n转换后：\n\n```css\n[data-v-xxx] .child-class {\n  color: blue;\n}\n```\n\n### ::v-slotted() 选择器\n\n为插槽内容应用样式．\n\n```vue\n<style scoped>\n::v-slotted(.slot-content) {\n  font-weight: bold;\n}\n</style>\n```\n\n转换后：\n\n```css\n.slot-content[data-v-xxx-s] {\n  font-weight: bold;\n}\n```\n\n`-s` 后缀表示\"slotted（插槽）\"．\n由于插槽内容来自父组件，\n使用特殊的插槽作用域 ID 而不是常规的作用域 ID．\n\n### :global() 选择器\n\n在 scoped 样式块中定义全局样式．\n\n```vue\n<style scoped>\n:global(.global-class) {\n  margin: 0;\n}\n</style>\n```\n\n转换后：\n\n```css\n.global-class {\n  margin: 0;\n}\n```\n\n## 使用 v-bind() 的动态样式\n\n可以在 CSS 中使用组件状态．\n\n```vue\n<script setup>\nimport { ref } from 'vue'\nconst color = ref('red')\n</script>\n\n<style scoped>\n.text {\n  color: v-bind(color);\n}\n</style>\n```\n\n转换后：\n\n```css\n.text[data-v-xxx] {\n  color: var(--xxx-color);\n}\n```\n\n`v-bind()` 被转换为 CSS 自定义属性（CSS 变量）．\n在运行时，CSS 变量的值作为组件的内联样式设置．\n\n### 使用复杂表达式\n\n通过引号包裹可以使用复杂的表达式．\n\n```vue\n<style scoped>\n.box {\n  width: v-bind('size + \"px\"');\n  background: v-bind('theme.colors.primary');\n}\n</style>\n```\n\n<KawaikoNote variant=\"warning\" title=\"v-bind() 的性能考虑\">\n\n`v-bind()` 是一个方便的功能，但有性能影响：\n\n- 每个 `v-bind()` 作为 CSS 自定义属性设置在内联样式中\n- 每次值更改时都会触发样式重新计算\n- 对于频繁更改的值，直接使用内联样式可能更高效\n\n对于动画或频繁更新，请考虑使用内联样式或 CSS 动画代替 `v-bind()`．\n\n</KawaikoNote>\n\n## 未来扩展\n\n还可以考虑以下功能：\n\n- **CSS Modules**：自动类名生成\n- **CSS-in-JS 集成**：增强动态样式\n\n<KawaikoNote variant=\"base\" title=\"尝试实现！\">\n\n参考本章介绍的原理，尝试自己实现 Scoped CSS！\\\n这也是学习如何使用 PostCSS 的好机会．\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/040_scoped_css)\n\n## 总结\n\n- Scoped CSS 为每个组件隔离样式\n- 生成唯一的 scopeId 并应用于模板和样式\n- 模板获得 `data-v-xxx` 属性，CSS 获得 `[data-v-xxx]` 选择器\n- 使用 PostCSS 转换选择器\n\n## 参考链接\n\n- [Vue.js - Scoped CSS](https://cn.vuejs.org/api/sfc-css-features.html#scoped-css) - Vue 官方文档\n- [PostCSS](https://postcss.org/) - CSS 转换工具\n"
  },
  {
    "path": "book/online-book/src/zh-cn/60-basic-sfc-compiler/050-props-destructure.md",
    "content": "# 支持 Props 解构\n\n::: info 关于本章\n本章介绍如何实现 Vue 3.5 的响应式 Props 解构功能．\\\n学习如何在解构 props 的同时保持响应性．\n:::\n\n## 什么是响应式 Props 解构？\n\n从 Vue 3.5 开始，你可以在 `<script setup>` 中解构 `defineProps` 的返回值．\n\n```vue\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\n这个功能使访问 props 更加简单．\n\n<KawaikoNote variant=\"question\" title=\"为什么需要特殊处理？\">\n\n在普通的 JavaScript 中，解构对象会复制值，并断开与原始对象的连接．\\\n但是 Vue 的 props 需要保持响应性．\\\n编译器将解构访问转换为 `__props.xxx` 访问来保持响应性！\n\n</KawaikoNote>\n\n## 工作原理\n\nProps 解构通过以下步骤实现：\n\n1. **模式检测**：检测 `const { ... } = defineProps(...)`\n2. **绑定注册**：将每个解构的属性注册为 `PROPS`\n3. **默认值处理**：将默认值转换为 `withDefaults` 等效处理\n4. **代码转换**：将 props 访问转换为 `__props.xxx`\n\n### 转换示例\n\n```vue\n<!-- 输入 -->\n<script setup>\nconst { count, message = 'hello' } = defineProps({\n  count: Number,\n  message: String\n})\n\nconsole.log(count, message)\n</script>\n```\n\n```ts\n// 输出\nexport default {\n  props: {\n    count: Number,\n    message: { type: String, default: 'hello' }\n  },\n  setup(__props) {\n    console.log(__props.count, __props.message)\n\n    return (_ctx) => {\n      // ...\n    }\n  }\n}\n```\n\n## 检测解构模式\n\n检测 `defineProps` 的返回值是否被赋值给 `ObjectPattern`（解构模式）．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\ninterface PropsDestructureBindings {\n  [key: string]: {\n    local: string      // 本地变量名\n    default?: string   // 默认值\n  }\n}\n\nlet propsDestructuredBindings: PropsDestructureBindings = Object.create(null)\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  propsRuntimeDecl = node.arguments[0]\n\n  // 处理解构模式\n  if (declId && declId.type === \"ObjectPattern\") {\n    processPropsDestructure(declId)\n  } else if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## 处理解构\n\n从 `ObjectPattern` 中提取每个属性并注册为绑定．\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"ObjectProperty\") {\n      const key = prop.key\n      const value = prop.value\n\n      // 获取属性名\n      let propKey: string\n      if (key.type === \"Identifier\") {\n        propKey = key.name\n      } else if (key.type === \"StringLiteral\") {\n        propKey = key.value\n      } else {\n        continue\n      }\n\n      // 处理本地变量名和默认值\n      let local: string\n      let defaultValue: string | undefined\n\n      if (value.type === \"Identifier\") {\n        // const { count } = defineProps(...)\n        local = value.name\n      } else if (value.type === \"AssignmentPattern\") {\n        // const { count = 0 } = defineProps(...)\n        if (value.left.type === \"Identifier\") {\n          local = value.left.name\n          defaultValue = scriptSetup!.content.slice(\n            value.right.start!,\n            value.right.end!\n          )\n        } else {\n          continue\n        }\n      } else {\n        continue\n      }\n\n      // 注册绑定\n      propsDestructuredBindings[propKey] = { local, default: defaultValue }\n      bindingMetadata[local] = BindingTypes.PROPS\n    }\n  }\n}\n```\n\n## 默认值处理\n\n当在解构中指定默认值时，将其合并到 props 定义中．\n\n```ts\nfunction genRuntimeProps(): string | undefined {\n  if (!propsRuntimeDecl) return undefined\n\n  let propsString = scriptSetup!.content.slice(\n    propsRuntimeDecl.start!,\n    propsRuntimeDecl.end!\n  )\n\n  // 如果有默认值则合并\n  const defaults: Record<string, string> = {}\n  for (const key in propsDestructuredBindings) {\n    const binding = propsDestructuredBindings[key]\n    if (binding.default) {\n      defaults[key] = binding.default\n    }\n  }\n\n  if (Object.keys(defaults).length > 0) {\n    // 相当于 withDefaults 的处理\n    propsString = mergeDefaults(propsString, defaults)\n  }\n\n  return propsString\n}\n\nfunction mergeDefaults(\n  propsString: string,\n  defaults: Record<string, string>\n): string {\n  // 实际实现通过操作 AST 来合并默认值\n  // 这里是简化示例\n  const ast = parseExpression(propsString)\n  // ... 合并默认值的处理\n  return generate(ast).code\n}\n```\n\n## 转换 Props 访问\n\n在模板和脚本中，将解构变量的访问转换为 `__props.xxx`．\n\n```ts\nfunction processPropsAccess(source: string): string {\n  const s = new MagicString(source)\n\n  // 遍历标识符并转换\n  walk(scriptSetupAst, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const binding = propsDestructuredBindings[node.name]\n        if (binding && binding.local === node.name) {\n          // 转换为 props 访问\n          s.overwrite(node.start!, node.end!, `__props.${node.name}`)\n        }\n      }\n    }\n  })\n\n  return s.toString()\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"编译器的魔法！\">\n\n解构在普通 JavaScript 中通常会失去响应性，\\\n但编译器将其转换为 `__props.xxx` 访问，\\\n使你可以将解构语法作为语法糖使用！\n\n</KawaikoNote>\n\n## Rest 模式支持\n\n也可以支持 `...rest` 模式．\n\n```vue\n<script setup>\nconst { id, ...attrs } = defineProps(['id', 'class', 'style'])\n</script>\n```\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"RestElement\") {\n      // 处理 rest 模式\n      if (prop.argument.type === \"Identifier\") {\n        const restName = prop.argument.name\n        // rest 需要特殊处理\n        // 实际上使用 computed 来获取剩余的 props\n        bindingMetadata[restName] = BindingTypes.SETUP_REACTIVE_CONST\n      }\n    }\n    // ...\n  }\n}\n```\n\n## 测试\n\n```vue\n<!-- Parent.vue -->\n<script setup>\nimport { ref } from 'chibivue'\nimport Child from './Child.vue'\n\nconst count = ref(0)\nconst message = ref('Hello')\n</script>\n\n<template>\n  <Child :count=\"count\" :message=\"message\" />\n  <button @click=\"count++\">Increment</button>\n</template>\n```\n\n```vue\n<!-- Child.vue -->\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n\n// count 和 message 被转换为 __props.count, __props.message\nconsole.log(count, message)\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\n## 未来扩展\n\n可以考虑以下功能：\n\n- **别名支持**：支持 `const { count: c } = defineProps(...)`\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/050_props_destructure)\n\n## 总结\n\n- Props 解构是 Vue 3.5 引入的功能\n- 检测解构模式并将每个属性注册为 `PROPS` 绑定\n- 默认值合并到 props 定义中\n- 将变量访问转换为 `__props.xxx` 以保持响应性\n\n## 参考链接\n\n- [Vue.js - 响应式 Props 解构](https://cn.vuejs.org/guide/components/props.html#reactive-props-destructure) - Vue 官方文档\n- [RFC - Reactive Props Destructure](https://github.com/vuejs/rfcs/discussions/502) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/zh-cn/60-basic-sfc-compiler/060-type-based-macros.md",
    "content": "# 基于类型的 defineProps / defineEmits\n\n::: info 关于本章\n本章介绍如何使用 TypeScript 类型参数实现 `defineProps` 和 `defineEmits`．\\\n学习如何从类型定义生成运行时定义．\n:::\n\n## 什么是基于类型的声明？\n\n在 Vue 3 中，你可以使用 TypeScript 泛型声明 `defineProps` 和 `defineEmits`．\n\n```vue\n<script setup lang=\"ts\">\n// 基于类型的 defineProps\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n\n// 基于类型的 defineEmits\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"为什么基于类型更方便？\">\n\n运行时声明使用 `Number`，`String` 等，\\\n但基于类型的声明可以直接使用 TypeScript 的类型系统！\\\nIDE 的补全和错误检查也更加强大．\n\n</KawaikoNote>\n\n## 工作原理\n\n基于类型的宏通过以下步骤处理：\n\n1. **类型参数检测**：检测 `defineProps<T>()` 中的泛型\n2. **类型解析**：解析 TypeScript 类型定义\n3. **运行时定义生成**：从类型生成运行时 props/emits\n4. **代码输出**：作为普通运行时声明输出\n\n### 转换示例\n\n```vue\n<!-- 输入 -->\n<script setup lang=\"ts\">\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n</script>\n```\n\n```ts\n// 输出\nexport default {\n  props: {\n    count: { type: Number, required: true },\n    message: { type: String, required: false }\n  },\n  setup(__props) {\n    // ...\n  }\n}\n```\n\n## 检测类型参数\n\n检测 `defineProps` 或 `defineEmits` 是否有类型参数．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nlet propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  // 检查类型参数\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    if (typeArg) {\n      propsTypeDecl = resolveTypeElements(typeArg)\n    }\n  } else {\n    // 运行时声明\n    propsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n```\n\n## 解析类型\n\n解析 TypeScript 类型字面量以提取属性信息．\n\n```ts\ninterface PropTypeData {\n  type: string[]      // 类型数组（支持联合类型）\n  required: boolean   // 是否必需\n}\n\nfunction extractPropsFromType(\n  typeDecl: TSTypeLiteral | TSInterfaceBody\n): Record<string, PropTypeData> {\n  const props: Record<string, PropTypeData> = {}\n\n  const members = typeDecl.type === \"TSTypeLiteral\"\n    ? typeDecl.members\n    : typeDecl.body\n\n  for (const member of members) {\n    if (member.type === \"TSPropertySignature\") {\n      const key = member.key\n      if (key.type !== \"Identifier\") continue\n\n      const propName = key.name\n      const isOptional = !!member.optional\n\n      // 解析类型\n      const types = member.typeAnnotation\n        ? resolveType(member.typeAnnotation.typeAnnotation)\n        : [\"null\"]\n\n      props[propName] = {\n        type: types,\n        required: !isOptional\n      }\n    }\n  }\n\n  return props\n}\n```\n\n## 类型到构造函数的转换\n\n将 TypeScript 类型转换为 JavaScript 构造函数．\n\n```ts\nfunction resolveType(node: TSType): string[] {\n  switch (node.type) {\n    case \"TSStringKeyword\":\n      return [\"String\"]\n\n    case \"TSNumberKeyword\":\n      return [\"Number\"]\n\n    case \"TSBooleanKeyword\":\n      return [\"Boolean\"]\n\n    case \"TSArrayType\":\n      return [\"Array\"]\n\n    case \"TSFunctionType\":\n      return [\"Function\"]\n\n    case \"TSObjectKeyword\":\n    case \"TSTypeLiteral\":\n      return [\"Object\"]\n\n    case \"TSUnionType\":\n      // 联合类型返回多个构造函数\n      const types: string[] = []\n      for (const t of node.types) {\n        // 排除 null/undefined\n        if (t.type === \"TSNullKeyword\" || t.type === \"TSUndefinedKeyword\") {\n          continue\n        }\n        types.push(...resolveType(t))\n      }\n      return types\n\n    case \"TSTypeReference\":\n      // 自定义类型和引用\n      if (node.typeName.type === \"Identifier\") {\n        const name = node.typeName.name\n        // 内置类型映射\n        if (name === \"Array\") return [\"Array\"]\n        if (name === \"Function\") return [\"Function\"]\n        if (name === \"Object\") return [\"Object\"]\n        // 其他保持原样\n        return [name]\n      }\n      return [\"Object\"]\n\n    default:\n      return [\"null\"]\n  }\n}\n```\n\n## 生成运行时定义\n\n从解析的类型信息生成运行时 props 定义．\n\n```ts\nfunction genRuntimePropsFromType(\n  propsDecl: Record<string, PropTypeData>\n): string {\n  const props: string[] = []\n\n  for (const [key, { type, required }] of Object.entries(propsDecl)) {\n    const typeStr = type.length === 1\n      ? type[0]\n      : `[${type.join(\", \")}]`\n\n    if (required) {\n      props.push(`${key}: { type: ${typeStr}, required: true }`)\n    } else {\n      props.push(`${key}: { type: ${typeStr}, required: false }`)\n    }\n  }\n\n  return `{ ${props.join(\", \")} }`\n}\n```\n\n## defineEmits 的类型处理\n\n`defineEmits` 同样处理类型参数．\n\n```ts\nlet emitsTypeDecl: TSFunctionType[] | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    emitsTypeDecl = resolveEmitsTypeElements(typeArg)\n  } else {\n    emitsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n\nfunction resolveEmitsTypeElements(\n  typeArg: TSType\n): TSFunctionType[] | undefined {\n  // 函数重载形式\n  if (typeArg.type === \"TSTypeLiteral\") {\n    return typeArg.members\n      .filter((m): m is TSCallSignatureDeclaration =>\n        m.type === \"TSCallSignatureDeclaration\"\n      )\n      .map(m => m as unknown as TSFunctionType)\n  }\n  return undefined\n}\n```\n\n## 生成 emits 运行时定义\n\n```ts\nfunction genRuntimeEmitsFromType(\n  emitsDecl: TSFunctionType[]\n): string {\n  const events: string[] = []\n\n  for (const sig of emitsDecl) {\n    // 第一个参数是事件名\n    const firstParam = sig.parameters?.[0]\n    if (firstParam?.type === \"Identifier\" && firstParam.typeAnnotation) {\n      const typeAnn = firstParam.typeAnnotation.typeAnnotation\n      if (typeAnn.type === \"TSLiteralType\" &&\n          typeAnn.literal.type === \"StringLiteral\") {\n        events.push(`\"${typeAnn.literal.value}\"`)\n      }\n    }\n  }\n\n  return `[${events.join(\", \")}]`\n}\n```\n\n### 转换示例\n\n```vue\n<!-- 输入 -->\n<script setup lang=\"ts\">\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n```ts\n// 输出\nexport default {\n  emits: ['change', 'update'],\n  setup(__props, { emit }) {\n    // ...\n  }\n}\n```\n\n## withDefaults 支持\n\n要为基于类型的 props 指定默认值，使用 `withDefaults`．\n\n```vue\n<script setup lang=\"ts\">\ninterface Props {\n  count: number\n  message?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  message: 'default message'\n})\n</script>\n```\n\n```ts\nconst WITH_DEFAULTS = \"withDefaults\"\n\nfunction processWithDefaults(node: Node): boolean {\n  if (!isCallOf(node, WITH_DEFAULTS)) {\n    return false\n  }\n\n  const [propsCall, defaultsArg] = node.arguments\n\n  // 处理 defineProps\n  if (isCallOf(propsCall, DEFINE_PROPS)) {\n    processDefineProps(propsCall)\n  }\n\n  // 保存默认值\n  if (defaultsArg) {\n    propsDefaults = defaultsArg\n  }\n\n  return true\n}\n```\n\n## 测试\n\n```vue\n<!-- TypedComponent.vue -->\n<script setup lang=\"ts\">\ninterface Props {\n  id: number\n  name: string\n  active?: boolean\n}\n\ninterface Emits {\n  (e: 'select', id: number): void\n  (e: 'update', name: string): void\n}\n\nconst props = defineProps<Props>()\nconst emit = defineEmits<Emits>()\n\nfunction handleClick() {\n  emit('select', props.id)\n}\n</script>\n\n<template>\n  <div @click=\"handleClick\">\n    {{ name }} ({{ active ? 'active' : 'inactive' }})\n  </div>\n</template>\n```\n\n## 未来扩展\n\n可以考虑以下功能：\n\n- **接口引用**：引用其他文件中定义的类型\n- **映射类型**：`Partial<T>` 等变换类型\n- **泛型组件**：带有泛型类型参数的组件\n- **仅类型导入**：处理 `import type`\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/060_type_based_macros)\n\n## 总结\n\n- 基于类型的 defineProps/defineEmits 使用 TypeScript 类型参数\n- 编译器解析类型并生成运行时定义\n- TypeScript 类型映射到 JavaScript 构造函数\n- 可以使用 withDefaults 指定默认值\n\n## 参考链接\n\n- [Vue.js - 组合式 API 与 TypeScript](https://cn.vuejs.org/guide/typescript/composition-api.html) - Vue 官方文档\n- [Vue.js - 仅类型 props/emit 声明](https://cn.vuejs.org/api/sfc-script-setup.html#type-only-props-emit-declarations) - Vue 官方文档\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/010-plugins/010-router.md",
    "content": "# 路由器\n\n## 什么是路由器？\n\n在单页应用（SPA）中，我们需要根据 URL 显示不同的组件．在 Vue.js 生态系统中，Vue Router 提供了这个功能．\n\n<KawaikoNote variant=\"question\" title=\"SPA 路由？\">\n\n在传统网站中，每次 URL 变化时都会从服务器获取新的 HTML 页面．\n在 SPA 中，页面切换由 JavaScript 处理，无需请求服务器即可更新屏幕．\n这被称为\"客户端路由\"．\n\n</KawaikoNote>\n\n在本章中，我们将实现基本的 Vue Router 功能，命名为 chibivue-router．\n\n## 包结构\n\nchibivue-router 位于 `@extensions/chibivue-router` 包中．\n\n```\n@extensions/chibivue-router/src/\n├── index.ts              # 导出\n├── router.ts             # 主路由逻辑\n├── history.ts            # History API 封装\n├── RouterView.ts         # RouterView 组件\n├── useApi.ts             # Composition API 钩子\n├── injectionSymbols.ts   # 依赖注入键\n└── types/\n    └── index.ts          # 类型定义\n```\n\n## 类型定义\n\n### RouteLocationNormalizedLoaded\n\n表示当前路由信息的类型．\n\n```ts\n// types/index.ts\nexport interface RouteLocationNormalizedLoaded {\n  fullPath: string;\n  component: any;\n}\n```\n\n### RouteRecord\n\n表示路由定义的类型．\n\n```ts\n// router.ts\nexport interface RouteRecord {\n  path: string;\n  component: any;\n}\n```\n\n### Router 接口\n\n定义路由器的公共 API．\n\n```ts\n// router.ts\nexport interface Router {\n  install(app: App): void;\n  push(to: string): void;\n  replace(to: string): void;\n}\n```\n\n## History API 抽象\n\n封装浏览器的 History API，使其更易于在路由器中使用．\n\n### RouterHistory 接口\n\n```ts\n// history.ts\nexport interface RouterHistory {\n  location: Location;\n  push(to: string): void;\n  replace(to: string): void;\n  go(delta: number, triggerListeners?: boolean): void;\n}\n```\n\n### createWebHistory 函数\n\n```ts\n// history.ts\nexport const createWebHistory = (): RouterHistory => {\n  return {\n    location: window.location,\n    push(to: string) {\n      window.history.pushState({}, \"\", to);\n    },\n    replace(to: string) {\n      window.history.replaceState({}, \"\", to);\n    },\n    go(delta: number, triggerListeners?: boolean) {\n      window.history.go(delta);\n    },\n  };\n};\n```\n\n要点：\n- `pushState`：向历史记录添加新条目（可以用后退按钮返回）\n- `replaceState`：替换当前历史记录条目（不会保留在历史记录中）\n- `go`：在历史记录中前进或后退\n\n<KawaikoNote variant=\"funny\" title=\"pushState vs replaceState\">\n\n可以把 `pushState` 想象成\"在书架上添加一本新书\"．\n`replaceState` 就像\"用另一本书替换你正在读的书\"．\n后退按钮就像\"回到你之前读的书\"．\n\n</KawaikoNote>\n\n## 依赖注入键\n\n定义用于通过 provide/inject 共享路由相关值的键．\n\n```ts\n// injectionSymbols.ts\nimport type { ComputedRef, InjectionKey, Ref } from \"@chibivue/runtime-core\";\nimport type { Router } from \"./router\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\n\n// 路由器本身\nexport const routerKey = Symbol() as InjectionKey<Router>;\n\n// 当前路由（包装在 computed 中）\nexport const routeLocationKey = Symbol() as InjectionKey<\n  ComputedRef<RouteLocationNormalizedLoaded>\n>;\n\n// RouterView 用的路由（Ref）\nexport const routerViewLocationKey = Symbol() as InjectionKey<\n  Ref<RouteLocationNormalizedLoaded>\n>;\n```\n\n需要三个独立键的原因：\n1. `routerKey`：用于访问导航方法（`push`，`replace`）\n2. `routeLocationKey`：用于通过 `useRoute()` 获取当前路由信息（通过 computed 实现响应式）\n3. `routerViewLocationKey`：用于 RouterView 组件确定显示哪个组件\n\n## createRouter 实现\n\n### 路由解析\n\n```ts\n// router.ts\nconst resolve = (to: string) => {\n  const route = options.routes.find((route) => route.path === to);\n  return {\n    fullPath: to,\n    component: route?.component ?? null,\n  };\n};\n```\n\n当前实现仅支持精确匹配．Vue Router 的实际实现还支持参数（`/user/:id`）和正则表达式．\n\n### 状态管理\n\n```ts\n// router.ts\nconst currentRoute = ref<RouteLocationNormalizedLoaded>({\n  fullPath: routerHistory.location.pathname,\n  component: resolve(routerHistory.location.pathname).component,\n});\n```\n\n当前路由信息使用 `ref` 管理．这使得路由变化时 RouterView 可以自动重新渲染．\n\n### 导航方法\n\n```ts\n// router.ts\nfunction push(to: string) {\n  routerHistory.push(to);\n  currentRoute.value = resolve(to);\n}\n\nfunction replace(to: string) {\n  routerHistory.replace(to);\n  currentRoute.value = resolve(to);\n}\n```\n\n同时更改 URL 和响应式状态．\n\n### 插件安装\n\n```ts\n// router.ts\ninstall(app: App) {\n  const router = this;\n\n  // 全局注册 RouterView 组件\n  app.component(\"RouterView\", RouterViewImpl);\n\n  // 创建响应式路由信息\n  const reactiveRoute = computed(() => currentRoute.value);\n\n  // 提供值\n  app.provide(routerKey, router);\n  app.provide(routeLocationKey, reactive(reactiveRoute));\n  app.provide(routerViewLocationKey, currentRoute);\n}\n```\n\n当调用 `app.use(router)` 时，会执行这个 `install` 方法．\n\n## RouterView 组件\n\n显示与当前路由对应的组件．\n\n```ts\n// RouterView.ts\nimport { type ComponentOptions, Fragment, h, inject } from \"chibivue\";\nimport { routerViewLocationKey } from \"./injectionSymbols\";\n\nexport const RouterViewImpl: ComponentOptions = {\n  name: \"RouterView\",\n  setup() {\n    const injectedRoute = inject(routerViewLocationKey)!;\n\n    return () => {\n      const ViewComponent = injectedRoute.value.component;\n\n      // 包装在 Fragment 中进行渲染\n      const component = h(Fragment, [\n        h(ViewComponent, { key: injectedRoute.value.fullPath }),\n      ]);\n\n      return component;\n    };\n  },\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"key 属性很重要！\">\n\n通过指定 `fullPath` 作为 `key`，每当路由变化时组件都会完全重新挂载．\n如果没有这个，相同的组件会被重用，`setup` 不会重新执行．\n\n</KawaikoNote>\n\n包装在 Fragment 中的原因是为了确保正确的子元素补丁行为．\n\n## Composition API 钩子\n\n### useRouter\n\n获取路由器实例．\n\n```ts\n// useApi.ts\nexport function useRouter(): Router {\n  return inject(routerKey)!;\n}\n```\n\n用法：\n```ts\nconst router = useRouter()\nrouter.push('/about')\n```\n\n### useRoute\n\n获取当前路由信息．\n\n```ts\n// useApi.ts\nexport function useRoute(): ComputedRef<RouteLocationNormalizedLoaded> {\n  return inject(routeLocationKey)!;\n}\n```\n\n用法：\n```ts\nconst route = useRoute()\nconsole.log(route.value.fullPath) // '/about'\n```\n\n## 使用示例\n\n### 路由器配置\n\n```ts\n// router.ts\nimport { createRouter, createWebHistory } from 'chibivue-router'\nimport Home from './pages/Home.vue'\nimport About from './pages/About.vue'\nimport Contact from './pages/Contact.vue'\n\nexport const router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '/', component: Home },\n    { path: '/about', component: About },\n    { path: '/contact', component: Contact },\n  ],\n})\n```\n\n### 注册到应用\n\n```ts\n// main.ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\nimport { router } from './router'\n\nconst app = createApp(App)\napp.use(router)\napp.mount('#app')\n```\n\n### 在模板中使用\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { useRouter } from 'chibivue-router'\n\nconst router = useRouter()\n</script>\n\n<template>\n  <header>\n    <nav>\n      <button @click=\"router.push('/')\">首页</button>\n      <button @click=\"router.push('/about')\">关于</button>\n      <button @click=\"router.push('/contact')\">联系</button>\n    </nav>\n  </header>\n\n  <main>\n    <RouterView />\n  </main>\n</template>\n```\n\n## 处理流程\n\n```\napp.use(router)\n  ↓\nrouter.install(app)\n  ├── app.component(\"RouterView\", RouterViewImpl)\n  ├── app.provide(routerKey, router)\n  ├── app.provide(routeLocationKey, ...)\n  └── app.provide(routerViewLocationKey, currentRoute)\n  ↓\nRouterView 渲染\n  ↓\ninject(routerViewLocationKey) 获取 currentRoute\n  ↓\n渲染 currentRoute.value.component\n\n--- 导航 ---\n\nrouter.push('/about')\n  ↓\nrouterHistory.push('/about')  ← URL 变化\n  ↓\ncurrentRoute.value = resolve('/about')  ← 状态更新\n  ↓\nRouterView 重新渲染\n  ↓\n显示新组件\n```\n\n## 未来扩展\n\n当前实现是最小化的，但 Vue Router 还有以下功能：\n\n1. **RouterLink 组件**：包装 `<a>` 标签的导航组件\n2. **路由参数**：动态片段如 `/user/:id`\n3. **查询参数**：解析 `?key=value`\n4. **导航守卫**：`beforeEach`，`afterEach` 等钩子\n5. **popstate 事件**：处理浏览器后退/前进按钮\n6. **嵌套路由**：定义子路由\n\n<KawaikoNote variant=\"surprise\" title=\"实现完成！\">\n\n我们完成了一个简单的路由器．\n用大约 100 行代码，我们实现了 SPA 路由．\n这应该是理解 Vue Router 工作原理的一个好起点．\n\n</KawaikoNote>\n\n## 总结\n\nchibivue-router 实现由以下部分组成：\n\n1. **History API 封装**：用 `createWebHistory` 抽象浏览器历史操作\n2. **响应式状态管理**：用 `ref` 管理当前路由\n3. **依赖注入**：通过 `provide/inject` 在组件树中共享路由信息\n4. **RouterView 组件**：动态显示与当前路由对应的组件\n5. **Composition API 钩子**：通过 `useRouter` 和 `useRoute` 轻松访问\n\n通过结合 Vue 的插件系统，provide/inject 和响应式系统，我们实现了客户端路由．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/010-plugins/020-preprocessors.md",
    "content": "# CSS 预处理器\n\n## 什么是预处理器？\n\nCSS 预处理器是将扩展的 CSS 语言（SCSS，Less，Stylus 等）转换为标准 CSS 的工具．这些语言提供了变量，嵌套，混入和函数等功能，使 CSS 编写更加高效．\n\n<KawaikoNote variant=\"question\" title=\"为什么使用预处理器？\">\n\n纯 CSS 有一些限制：\n- 没有变量（CSS 自定义属性是后来添加的）\n- 没有嵌套\n- 代码复用困难\n\n预处理器解决了这些问题，使编写可维护的样式表成为可能．\n\n</KawaikoNote>\n\n在 Vue SFC 中，你可以通过在 `<style>` 块上指定 `lang` 属性来使用预处理器．\n\n```vue\n<style lang=\"scss\">\n$primary-color: #42b883;\n\n.container {\n  .title {\n    color: $primary-color;\n  }\n}\n</style>\n```\n\n## 支持的预处理器\n\nVue/chibivue 支持以下预处理器：\n\n| 预处理器 | lang 属性 | 特点 |\n|---------|----------|------|\n| **SCSS** | `scss` | 类 CSS 语法，变量，嵌套，混入 |\n| **Sass** | `sass` | 基于缩进的语法（无花括号）|\n| **Less** | `less` | 变量（`@`），混入，函数 |\n| **Stylus** | `styl`, `stylus` | 灵活语法，可选分隔符 |\n\n## 类型定义\n\n### StylePreprocessor\n\n预处理器的通用接口．\n\n```ts\n// style/preprocessors.ts\nexport type StylePreprocessor = (\n  source: string,\n  map: RawSourceMap | undefined,\n  options: {\n    [key: string]: any;\n    additionalData?: string | ((source: string, filename: string) => string);\n    filename: string;\n  },\n  customRequire: (id: string) => any,\n) => StylePreprocessorResults;\n```\n\n### StylePreprocessorResults\n\n表示预处理器结果的类型．\n\n```ts\nexport interface StylePreprocessorResults {\n  code: string;           // 转换后的 CSS\n  map?: object;          // Source map\n  errors: Error[];       // 错误列表\n  dependencies: string[]; // 依赖文件（@import 等）\n}\n```\n\n`dependencies` 很重要．它使 Vite 等工具能够在预处理器中通过 `@import` 导入的文件发生变化时触发重新构建．\n\n## 处理流程\n\n```\nSFC 文件 (.vue)\n    ↓\n[SFC 解析器] - 检测 <style lang=\"scss\">\n    ↓\n[compileStyle]\n    ↓\n1. 选择预处理器\n   processors[preprocessLang] → scss 预处理器\n    ↓\n2. 使用预处理器转换\n   SCSS/Sass/Less/Stylus → CSS\n    ↓\n3. PostCSS 管道\n   ├── cssVarsPlugin (v-bind 处理)\n   ├── trimPlugin (空白删除)\n   └── scopedPlugin (scoped CSS)\n    ↓\n4. 返回结果\n   { code, map, errors, dependencies }\n```\n\n## 预处理器实现\n\n### SCSS 预处理器\n\n```ts\n// style/preprocessors.ts\nconst scss: StylePreprocessor = (source, map, options, load = require) => {\n  // 动态加载 Dart Sass 库\n  const nodeSass: typeof import(\"sass\") = load(\"sass\");\n  const { compileString, renderSync } = nodeSass;\n\n  // 应用 additionalData（注入公共变量等）\n  const data = getSource(source, options.filename, options.additionalData);\n\n  let css: string;\n  let dependencies: string[];\n  let sourceMap: any;\n\n  try {\n    if (compileString) {\n      // 新 API（Sass 1.55.0+）\n      const result = compileString(data, {\n        ...options,\n        url: pathToFileURL(options.filename),\n        sourceMap: !!map,\n      });\n      css = result.css;\n      dependencies = result.loadedUrls.map((url) => fileURLToPath(url));\n      sourceMap = map ? result.sourceMap! : undefined;\n    } else {\n      // 旧 API（向后兼容）\n      const result = renderSync({\n        ...options,\n        data,\n        file: options.filename,\n        outFile: options.filename,\n        sourceMap: !!map,\n      });\n      css = result.css.toString();\n      dependencies = result.stats.includedFiles;\n      sourceMap = map ? JSON.parse(result.map!.toString()) : undefined;\n    }\n\n    // 合并 source map\n    if (map) {\n      return {\n        code: css,\n        errors: [],\n        dependencies,\n        map: merge(map, sourceMap!),\n      };\n    }\n    return { code: css, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"API 兼容性\">\n\nSass 有两套 API：旧版和新版．\n`compileString` 是新 API，`renderSync` 是旧 API．\n同时支持两者确保与任何 Sass 版本兼容．\n\n</KawaikoNote>\n\n### Sass 预处理器\n\nSass 使用与 SCSS 相同的引擎，但使用基于缩进的语法．\n\n```ts\nconst sass: StylePreprocessor = (source, map, options, load) =>\n  scss(\n    source,\n    map,\n    {\n      ...options,\n      indentedSyntax: true,  // 启用缩进语法\n    },\n    load,\n  );\n```\n\n### Less 预处理器\n\n```ts\nconst less: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeLess = load(\"less\");\n\n  let result: any;\n  let error: Error | null = null;\n\n  // Less 的 render 是异步的，但 syncImport: true 使其同步\n  nodeLess.render(\n    getSource(source, options.filename, options.additionalData),\n    { ...options, syncImport: true },\n    (err: Error | null, output: any) => {\n      error = err;\n      result = output;\n    },\n  );\n\n  if (error) return { code: \"\", errors: [error], dependencies: [] };\n\n  // Less 通过 imports 属性返回依赖\n  const dependencies = result.imports;\n\n  if (map) {\n    return {\n      code: result.css.toString(),\n      map: merge(map, result.map),\n      errors: [],\n      dependencies,\n    };\n  }\n\n  return {\n    code: result.css.toString(),\n    errors: [],\n    dependencies,\n  };\n};\n```\n\n### Stylus 预处理器\n\n```ts\nconst styl: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeStylus = load(\"stylus\");\n\n  try {\n    const ref = nodeStylus(source, options);\n\n    // 配置 source map\n    if (map) ref.set(\"sourcemap\", { inline: false, comment: false });\n\n    const result = ref.render();\n    const dependencies = ref.deps();  // 获取依赖\n\n    if (map) {\n      return {\n        code: result,\n        map: merge(map, ref.sourcemap),\n        errors: [],\n        dependencies,\n      };\n    }\n\n    return { code: result, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n## 使用 additionalData 注入公共样式\n\n`additionalData` 选项允许你向所有样式文件注入公共代码．\n\n```ts\nfunction getSource(\n  source: string,\n  filename: string,\n  additionalData?: string | ((source: string, filename: string) => string),\n) {\n  if (!additionalData) return source;\n\n  // 如果是函数，动态生成\n  if (isFunction(additionalData)) {\n    return additionalData(source, filename);\n  }\n\n  // 如果是字符串，添加到源代码前面\n  return additionalData + source;\n}\n```\n\n使用示例（Vite 配置）：\n\n```ts\n// vite.config.ts\nexport default defineConfig({\n  css: {\n    preprocessorOptions: {\n      scss: {\n        // 向所有 SCSS 文件注入变量\n        additionalData: `@import \"@/styles/variables.scss\";`,\n      },\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"funny\" title=\"注入全局变量\">\n\n`additionalData` 就像\"自动复制粘贴到每个样式文件的开头\"．\n它省去了每次都要导入变量和混入的麻烦．\n\n</KawaikoNote>\n\n## 预处理器注册\n\n```ts\nexport type PreprocessLang = \"less\" | \"sass\" | \"scss\" | \"styl\" | \"stylus\";\n\nexport const processors: Record<PreprocessLang, StylePreprocessor> = {\n  less,\n  sass,\n  scss,\n  styl,\n  stylus: styl,  // 别名\n};\n```\n\n## 在 compileStyle 中的集成\n\n预处理器在 `compileStyle` 函数中被调用．\n\n```ts\n// compileStyle.ts\nexport function doCompileStyle(options: SFCAsyncStyleCompileOptions) {\n  const {\n    filename,\n    id,\n    scoped = false,\n    trim = true,\n    preprocessLang,\n    // ...\n  } = options;\n\n  // 选择预处理器\n  const preprocessor = preprocessLang && processors[preprocessLang];\n\n  // 如果存在则执行预处理器\n  const preProcessedSource = preprocessor && preprocess(options, preprocessor);\n\n  // 获取 source map（来自预处理器或输入）\n  const map = preProcessedSource ? preProcessedSource.map : options.inMap;\n\n  // CSS 源代码（转换后的或原始的）\n  const source = preProcessedSource ? preProcessedSource.code : options.source;\n\n  // 构建 PostCSS 管道\n  const plugins = (postcssPlugins || []).slice();\n  plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));\n  if (trim) plugins.push(trimPlugin());\n  if (scoped) plugins.push(scopedPlugin(longId));\n\n  // 收集依赖\n  const dependencies = new Set(\n    preProcessedSource ? preProcessedSource.dependencies : []\n  );\n\n  // 使用 PostCSS 处理\n  const result = postcss(plugins).process(source, postCSSOptions);\n\n  return {\n    code: result.css,\n    map: result.map?.toJSON(),\n    errors: [...errors],\n    dependencies,\n  };\n}\n```\n\n## 使用示例\n\n### SCSS\n\n```vue\n<style lang=\"scss\">\n$primary: #42b883;\n$secondary: #35495e;\n\n.card {\n  background: $secondary;\n\n  .title {\n    color: $primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px rgba($secondary, 0.3);\n  }\n}\n</style>\n```\n\n### Less\n\n```vue\n<style lang=\"less\">\n@primary: #42b883;\n@secondary: #35495e;\n\n.card {\n  background: @secondary;\n\n  .title {\n    color: @primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px fade(@secondary, 30%);\n  }\n}\n</style>\n```\n\n### Stylus\n\n```vue\n<style lang=\"stylus\">\nprimary = #42b883\nsecondary = #35495e\n\n.card\n  background secondary\n\n  .title\n    color primary\n    font-size 1.5rem\n\n  &:hover\n    box-shadow 0 4px 8px rgba(secondary, 0.3)\n</style>\n```\n\n## Source Map 链接\n\n预处理器和 PostCSS 都会生成 source map．我们使用 `merge-source-map` 库来正确地链接它们．\n\n```\nSCSS 源代码\n    ↓ [SCSS → CSS]\n    ↓ Source map A\nCSS\n    ↓ [PostCSS]\n    ↓ Source map B\n最终 CSS\n    ↓\nmerge(A, B) → 最终 source map\n```\n\n这使得浏览器开发工具在调试时可以显示原始 SCSS/Less/Stylus 文件的行号．\n\n<KawaikoNote variant=\"surprise\" title=\"调试更轻松！\">\n\n有了 source map，当你在浏览器中想知道\"这个 CSS 是从哪里来的？\"时，\n你可以看到转换前原始 SCSS 文件中的确切位置．\n\n</KawaikoNote>\n\n## 总结\n\nCSS 预处理器实现由以下部分组成：\n\n1. **通用接口**：用 `StylePreprocessor` 类型抽象每个预处理器\n2. **动态加载**：用 `require()` 或 `customRequire` 加载预处理器\n3. **additionalData**：注入公共样式（变量，混入等）\n4. **依赖追踪**：收集 `@import` 的文件以支持热重载\n5. **Source map 链接**：合并预处理器和 PostCSS 的 source map\n6. **PostCSS 集成**：将预处理器输出传递给 PostCSS 管道\n\nVue/chibivue SFC 编译器抽象了预处理器，允许用户使用他们喜欢的 CSS 语言．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/010-plugins/020-store.md",
    "content": "# Store\n\n## 什么是 Store？\n\n随着应用程序变得越来越大，您通常需要在多个组件之间共享状态．在 Vue.js 生态系统中，Pinia 提供了这个功能．\n\n在本章中，我们将实现 Pinia 的基本功能作为 chibivue-store．\n\n### 为什么需要库？\n\n如果您只是想在组件之间共享状态，在模块作用域导出 `ref` 和 `computed` 就足够了：\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\n\nexport const count = ref(0);\nexport const doubleCount = computed(() => count.value * 2);\nexport const increment = () => count.value++;\n```\n\n这在 CSR（客户端渲染）中没有问题．但是，在 SSR（服务器端渲染）中会导致严重的问题．\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\n在 SSR 中，您必须注意\"**Cross-Request State Pollution**（跨请求状态污染）\"．\n\n由于服务器只初始化模块一次，上述模块作用域的状态会**在所有请求之间共享**．\n这可能导致一个用户的状态泄漏给另一个用户．\n\n</KawaikoNote>\n\n使用像 Pinia 这样的状态管理库，只需在 setup 中调用 `useXxxStore()`，库就会自动处理每个请求的状态隔离．\n\n<KawaikoNote variant=\"info\" title=\"如果您使用 Nuxt\">\n\n如果您使用 Nuxt，它提供了 [useState](https://nuxt.com/docs/api/composables/use-state)，一个 SSR 友好的状态管理组合式函数．\n对于简单的状态共享，`useState` 可能足够，无需引入 Pinia．\n\n</KawaikoNote>\n\n本章涵盖从基本的 CSR 使用到 SSR 水合．\n\n有关 SSR 的更多详细信息，请参阅 [SSR 章节](/zh-cn/90-web-application-essentials/020-ssr/010-create-ssr-app)．\n\n## 包结构\n\nchibivue-store 在 `@extensions/chibivue-store` 包中提供．\n\n```\n@extensions/chibivue-store/src/\n├── index.ts           # 导出\n├── createStore.ts     # 根 store 创建\n├── rootStore.ts       # Store 接口和符号\n└── store.ts           # defineStore 实现\n```\n\n## 类型定义\n\n### StateTree\n\n表示 store 持有的状态的类型．\n\n```ts\n// rootStore.ts\nexport type StateTree = Record<string | number | symbol, any>;\n```\n\n### Store 接口\n\n定义根 store 的公共 API．\n\n```ts\n// rootStore.ts\nexport interface Store {\n  install: (app: App) => void;\n  use(plugin: StorePlugin): Store;\n  state: Ref<Record<string, StateTree>>;\n  _p: StorePlugin[];\n  _a: App | null;\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n- `install`: 作为 Vue 插件的安装方法\n- `use`: 添加插件的方法\n- `state`: 保存所有 store 状态的 ref（用于 SSR）\n- `_p`: 已安装的插件\n- `_a`: 链接到此 store 的 App\n- `_e`: store 附加的 EffectScope\n- `_s`: 按 ID 管理已定义 store 的 Map\n\n### StoreInstance 接口\n\n定义每个 store 实例可用的方法．\n\n```ts\n// store.ts\nexport interface StoreInstance<\n  Id extends string = string,\n  S extends StateTree = StateTree,\n  G extends _GettersTree<S> = _GettersTree<S>,\n  A = Record<string, (...args: any[]) => any>,\n> {\n  $id: Id;\n  $state: S;\n  $patch: (partialState: Partial<S> | ((state: S) => void)) => void;\n  $reset: () => void;\n}\n```\n\n- `$id`: Store 标识符\n- `$state`: Store 状态（仅 Options API 风格）\n- `$patch`: 批量状态更新\n- `$reset`: 重置状态为初始值（仅 Options API 风格）\n\n## 依赖注入键\n\n定义通过 provide/inject 共享 store 的键．\n\n```ts\n// rootStore.ts\nimport type { InjectionKey } from \"chibivue\";\n\nexport const storeSymbol: InjectionKey<Store> = Symbol();\n```\n\n此符号用于在整个应用程序中 provide 由 `createStore()` 创建的 store．\n\n## createStore 实现\n\n创建根 store 的函数．\n\n```ts\n// createStore.ts\nimport { effectScope, markRaw, ref } from \"chibivue\";\nimport { type Store, setActiveStore, storeSymbol } from \"./rootStore\";\n\nexport function createStore(): Store {\n  const scope = effectScope();\n\n  const state = scope.run(() => ref({}))!;\n\n  let _p: StorePlugin[] = [];\n  let toBeInstalled: StorePlugin[] = [];\n\n  const store: Store = markRaw({\n    install(app) {\n      setActiveStore(store);\n      store._a = app;\n      app.provide(storeSymbol, store);\n      toBeInstalled.forEach((plugin) => _p.push(plugin));\n      toBeInstalled = [];\n    },\n\n    use(plugin) {\n      if (!this._a) {\n        toBeInstalled.push(plugin);\n      } else {\n        _p.push(plugin);\n      }\n      return this;\n    },\n\n    _p,\n    _a: null,\n    _e: scope,\n    _s: new Map(),\n    state,\n  });\n\n  return store;\n}\n```\n\n关键点：\n- `effectScope()` 创建 detached scope，管理 store 的生命周期\n- `state` 是 `ref({})`，集中管理所有 store 的状态（用于 SSR）\n- `markRaw` 使 store 对象本身不被响应式化\n- `install` 方法调用 `app.provide` 使 store 在整个应用程序中可用\n\n### 管理 activeStore\n\n```ts\n// rootStore.ts\nexport let activeStore: Store | undefined;\nexport const setActiveStore = (store: Store | undefined): Store | undefined =>\n  (activeStore = store);\n\nexport const getActiveStore = (): Store | undefined => {\n  const store = hasInjectionContext() && inject(storeSymbol, null);\n\n  if (__DEV__ && !store && typeof window === \"undefined\") {\n    console.warn(\n      `[chibivue-store]: Store instance not found in context. ` +\n      `This falls back to the global activeStore which exposes you to ` +\n      `cross-request state pollution on the server.`,\n    );\n  }\n\n  return store || activeStore;\n};\n```\n\n`activeStore` 用于从组件外部访问 store（例如，在其他 store 内部）．\n\n`getActiveStore` 使用 `hasInjectionContext()` 确认 injection context，在 SSR 环境中如果没有 context 则发出警告．这可以让开发者了解 Cross-Request State Pollution 的风险．\n\n## defineStore 实现\n\n定义单个 store 的函数．与 Pinia 一样，它支持两种定义风格．\n\n### Composition API 风格\n\n```ts\n// Composition API style (setup function)\nexport function defineStore<Id extends string, SS extends StateTree>(\n  id: Id,\n  setup: () => SS,\n): () => SS;\n```\n\n传递 `setup` 函数并使用 `ref` 和 `computed` 定义状态．\n\n### Options API 风格\n\n```ts\n// Options API style\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(options: StoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>;\n```\n\n使用包含 `state`，`getters` 和 `actions` 的对象定义．\n\n## 使用示例\n\n### Composition API 风格\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", () => {\n  // State\n  const count = ref(0);\n\n  // Getters（使用 computed）\n  const doubleCount = computed(() => count.value * 2);\n\n  // Actions\n  const increment = () => {\n    count.value++;\n  };\n\n  const reset = () => {\n    count.value = 0;\n  };\n\n  return {\n    count,\n    doubleCount,\n    increment,\n    reset,\n  };\n});\n```\n\n### Options API 风格\n\n```ts\n// stores/counter.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", {\n  state: () => ({\n    count: 0,\n  }),\n\n  getters: {\n    doubleCount(state) {\n      return state.count * 2;\n    },\n  },\n\n  actions: {\n    increment() {\n      this.count++;\n    },\n  },\n});\n```\n\n### 在应用程序中注册\n\n```ts\n// main.ts\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\nimport { createStore } from \"chibivue-store\";\n\nconst app = createApp(App);\napp.use(createStore());\napp.mount(\"#app\");\n```\n\n### 在组件中使用\n\n```vue\n<!-- Counter.vue -->\n<script setup>\nimport { useCounterStore } from \"../stores/counter\";\n\nconst counterStore = useCounterStore();\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ counterStore.count }}</p>\n    <p>Double: {{ counterStore.doubleCount }}</p>\n    <button @click=\"counterStore.increment\">Increment</button>\n  </div>\n</template>\n```\n\n## 使用 $patch\n\n`$patch` 允许一次更新多个状态属性．\n\n### 对象形式\n\n```ts\nconst store = useCounterStore();\n\nstore.$patch({\n  count: 10,\n});\n```\n\n### 函数形式\n\n```ts\nconst store = useCounterStore();\n\nstore.$patch((state) => {\n  state.count += 5;\n});\n```\n\n## 使用 $reset\n\n对于使用 Options API 风格定义的 store，`$reset` 将状态重置为初始值．\n\n```ts\nconst store = useCounterStore();\n\nstore.increment(); // count: 1\nstore.increment(); // count: 2\n\nstore.$reset(); // count: 0（回到初始值）\n```\n\n## SSR 支持\n\nchibivue-store 支持服务器端渲染（SSR）．\n\n### store.state 属性\n\n根 store 的 `state` 属性允许您序列化和水合所有 store 状态．\n\n```ts\n// Store interface\ninterface Store {\n  install: (app: App) => void;\n  state: Ref<Record<string, StateTree>>;  // 保存所有 store 的状态\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n`state` 作为 `ref({})` 创建，每个 store 的状态保存在 `state.value[storeId]` 中．\n这样可以：\n- SSR 序列化服务器端状态: `JSON.stringify(store.state.value)`\n- 客户端水合: `store.state.value = serverState`\n\n### 服务器端：序列化状态\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // 重要：为每个请求创建新实例\n  // 这可以防止 Cross-Request State Pollution\n  const store = createStore();\n  const app = createApp(App);\n  app.use(store);\n\n  const html = await renderToString(app);\n\n  // 序列化 store 状态\n  const storeState = JSON.stringify(store.state.value);\n\n  return { html, storeState };\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"每个请求新实例\">\n\n注意 `createStore()` 和 `createApp()` 是在 `render()` 函数内部调用的．\n**您不能在模块作用域创建它们作为单例**．\n\n```ts\n// 错误：在模块作用域创建是危险的\nconst store = createStore();  // 在所有请求之间共享！\nconst app = createApp(App);\n\nexport async function render() {\n  // store 和 app 在所有请求之间共享\n}\n```\n\n</KawaikoNote>\n\n### 嵌入 HTML\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.__STORE_STATE__ = ${storeState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### 客户端：水合状态\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nconst store = createStore();\nconst app = createApp(App);\napp.use(store);\n\n// 使用服务器状态水合\nif (window.__STORE_STATE__) {\n  store.state.value = window.__STORE_STATE__;\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR Ready!\">\n\nchibivue-store 现在支持 SSR．\n通过将服务器计算的状态传输到客户端，您可以在水合后保持一致的状态．\n\n</KawaikoNote>\n\n## 未来扩展\n\n当前实现涵盖了基本功能，但 Pinia 还有：\n\n1. **$subscribe**: 订阅状态变更\n2. **$onAction**: 监控 action 执行\n3. **插件系统**: 扩展 store 功能\n4. **Devtools 集成**: 状态可视化和时间旅行调试\n5. **mapState / mapActions**: Options API 组件的辅助函数\n\n## 总结\n\nchibivue-store 实现包括：\n\n1. **根 Store 创建**: 使用 `createStore` 作为 Vue 插件安装\n2. **依赖注入**: 通过 `provide/inject` 在组件树中共享 store\n3. **两种定义风格**: 支持 Composition API 和 Options API\n4. **Getters**: 使用 `computed` 定义派生状态\n5. **Actions**: 可以访问 state 和 getters 的方法\n6. **$patch**: 批量状态更新\n7. **$reset**: 重置状态为初始值（仅 Options API）\n8. **单例模式**: 每个 store ID 只创建一个实例\n9. **SSR 支持**: 通过 `store.state` 序列化和水合状态\n\n通过结合 Vue 的插件系统，provide/inject 和响应式系统，我们实现了全局状态管理．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/010-plugins/030-data-fetch.md",
    "content": "# Data Fetch\n\n## 什么是数据获取库？\n\n现代 Web 应用程序频繁地从服务器获取数据．在 Vue.js 生态系统中，Pinia Colada 和 TanStack Query 等库提供了这个功能．\n\n在本章中，我们将实现类似 Pinia Colada 的基本数据获取功能，作为 chibivue-fetch．\n\n### 为什么需要库？\n\n简单的数据获取用 `fetch` 和 `ref` 似乎就足够了：\n\n```ts\n// composables/useUser.ts\nimport { ref, onMounted } from \"chibivue\";\n\nexport function useUser(id: number) {\n  const user = ref(null);\n  const isLoading = ref(true);\n  const error = ref(null);\n\n  onMounted(async () => {\n    try {\n      const response = await fetch(`/api/users/${id}`);\n      user.value = await response.json();\n    } catch (e) {\n      error.value = e;\n    } finally {\n      isLoading.value = false;\n    }\n  });\n\n  return { user, isLoading, error };\n}\n```\n\n但是，这个实现有以下问题：\n\n1. **没有缓存**：相同的数据会被多次获取\n2. **SSR 困难**：无法将服务器获取的数据传输到客户端\n3. **重复请求**：相同组件多次挂载会导致重复请求\n4. **错误处理**：重试和重新获取的逻辑变得复杂\n\n数据获取库解决了这些问题，并提供了声明式的 API．\n\n## 包结构\n\nchibivue-fetch 在 `@extensions/chibivue-fetch` 包中提供．\n\n```\n@extensions/chibivue-fetch/src/\n├── index.ts           # 导出\n├── queryCache.ts      # QueryCache 实现（缓存管理）\n├── useQuery.ts        # 数据获取 hook\n├── useMutation.ts     # 数据变更 hook\n└── types.ts           # 类型定义\n```\n\n## Data State 模式\n\n与 Pinia Colada 类似，chibivue-fetch 用三种状态表示数据状态：\n\n```ts\ntype DataStateStatus = \"pending\" | \"error\" | \"success\";\n\ntype DataState<TData, TError> =\n  | { status: \"pending\"; data: undefined; error: null }\n  | { status: \"error\"; data: TData | undefined; error: TError }\n  | { status: \"success\"; data: TData; error: null };\n```\n\n这种状态模型可以清楚地追踪数据状态．\n\n## QueryCache\n\n`QueryCache` 负责缓存管理和 SSR 的状态管理．\n\n```ts\n// queryCache.ts\nexport interface QueryCache {\n  install: (app: App) => void;\n  caches: Map<string, UseQueryEntry>;\n  options: Required<QueryCacheOptions>;\n  create: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults | null, ...) => UseQueryEntry;\n  ensure: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults) => UseQueryEntry;\n  fetch: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  refresh: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  invalidate: (entry: UseQueryEntry) => void;\n  invalidateQueries: (key?: EntryKey) => void;\n  remove: (entry: UseQueryEntry) => void;\n  track: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  untrack: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  setQueryData: <TData>(key: EntryKey, data: TData) => void;\n  getQueryData: <TData>(key: EntryKey) => TData | undefined;\n  prefetchQuery: <TData>(key: EntryKey, queryFn: (ctx: QueryContext) => Promise<TData>, options?: Partial<UseQueryOptionsWithDefaults>) => Promise<void>;\n  isStale: (entry: UseQueryEntry) => boolean;\n}\n```\n\n### 主要方法\n\n- `ensure`: 获取或创建条目\n- `fetch`: 执行查询（总是执行）\n- `refresh`: 刷新查询（仅在 stale 或 error 时执行）\n- `invalidate`: 使条目失效（标记为 stale）\n- `invalidateQueries`: 使匹配键的条目失效\n- `track` / `untrack`: 追踪组件依赖关系\n- `setQueryData` / `getQueryData`: 直接操作缓存数据\n- `prefetchQuery`: 预先获取数据并存储到缓存\n\n### createQueryCache\n\n```ts\nimport { createQueryCache } from \"chibivue-fetch\";\n\nconst queryCache = createQueryCache({\n  staleTime: 5000,       // 默认的 stale time (5秒)\n  gcTime: 300000,        // 默认的 GC time (5分钟)\n});\n\napp.use(queryCache);\n```\n\n## useQuery\n\n`useQuery` 是用于数据获取的组合式函数．\n\n```ts\n// useQuery.ts\nexport interface UseQueryOptions<TData = unknown, TError = Error> {\n  key: EntryKey | EntryKeyFn;\n  query: (context: QueryContext) => Promise<TData>;\n  staleTime?: number;\n  gcTime?: number;\n  refetchOnMount?: boolean | \"always\";\n  initialData?: TData | (() => TData);\n  enabled?: boolean | Ref<boolean> | ComputedRef<boolean>;\n  retry?: number | boolean;\n  retryDelay?: number;\n  meta?: QueryMeta;\n}\n\nexport interface UseQueryReturn<TData = unknown, TError = Error> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  refresh: () => Promise<DataState<TData, TError>>;\n  refetch: () => Promise<DataState<TData, TError>>;\n}\n```\n\n### 选项\n\n- `key`: 查询的唯一键（作为缓存键使用）\n- `query`: 获取数据的异步函数（接收 `{ signal }`）\n- `staleTime`: 数据变为「stale（过期）」的时间\n- `gcTime`: 保留未使用缓存的期间（垃圾回收）\n- `enabled`: 是否启用查询\n- `retry`: 错误时的重试次数\n- `initialData`: 初始数据\n\n### 状态\n\n- `status`: 当前状态（`\"pending\"` | `\"error\"` | `\"success\"`）\n- `asyncStatus`: 异步状态（`\"idle\"` | `\"loading\"`）\n- `isPending`: 还没有初始数据\n- `isLoading`: 初次获取中（`isPending` 且 `asyncStatus === \"loading\"`）\n- `isSuccess`: 获取成功\n- `isError`: 获取失败\n\n### refresh 和 refetch 的区别\n\n- `refresh()`: 仅在 stale 或 error 时获取\n- `refetch()`: 总是获取（先使缓存失效）\n\n### 使用示例\n\n```ts\nimport { useQuery } from \"chibivue-fetch\";\n\nconst { data, isLoading, error, refresh } = useQuery({\n  key: [\"user\", userId],\n  query: ({ signal }) => fetch(`/api/users/${userId}`, { signal }).then((res) => res.json()),\n  staleTime: 60000, // 1分钟内使用缓存\n});\n```\n\n## useMutation\n\n`useMutation` 是用于数据变更（POST，PUT，DELETE 等）的组合式函数．\n\n```ts\n// useMutation.ts\nexport interface UseMutationOptions<TData, TError, TVariables, TContext> {\n  mutation: (variables: TVariables) => Promise<TData>;\n  onMutate?: (variables: TVariables) => TContext | Promise<TContext>;\n  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n}\n\nexport interface UseMutationReturn<TData, TError, TVariables> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  variables: ShallowRef<TVariables | undefined>;\n  mutate: (variables: TVariables) => void;\n  mutateAsync: (variables: TVariables) => Promise<TData>;\n  reset: () => void;\n}\n```\n\n### 生命周期回调\n\n- `onMutate`: mutation 执行前调用（返回 context）\n- `onSuccess`: 成功时调用\n- `onError`: 错误时调用\n- `onSettled`: 成功或错误后最后调用\n\n### 使用示例\n\n```ts\nimport { useMutation } from \"chibivue-fetch\";\n\nconst { mutate, isLoading, isSuccess } = useMutation({\n  mutation: (newUser) => fetch(\"/api/users\", {\n    method: \"POST\",\n    body: JSON.stringify(newUser),\n  }).then((res) => res.json()),\n  onSuccess: (data) => {\n    console.log(\"User created:\", data);\n    // 使缓存失效以触发重新获取\n    queryCache.invalidateQueries([\"users\"]);\n  },\n});\n\n// 使用\nmutate({ name: \"John\", email: \"john@example.com\" });\n```\n\n## 缓存的工作方式\n\n### Entry Key\n\n`key` 作为缓存键使用．数组格式可以表示层级式的键：\n\n```ts\n// 简单的键\nkey: [\"users\"]\n\n// 层级式的键\nkey: [\"users\", userId]\n\n// 包含对象的键\nkey: [\"users\", { status: \"active\", page: 1 }]\n```\n\n具有相同 `key` 的查询共享缓存．键会被序列化为排序后的 JSON，因此对象属性的顺序不重要．\n\n### Stale Time 和 GC Time\n\n```\n       ← staleTime →|← refetch window →|← gcTime →|\n  fetch             stale               inactive   gc\n    |-----------------|----------------------|-----|\n    data arrives      data is stale         data removed\n```\n\n- **staleTime**: 数据保持「fresh」的期间．在此期间调用 `refresh()` 不会重新获取\n- **gcTime**: 保留未使用缓存的期间．组件卸载后，经过此期间缓存会被删除\n\n```ts\n// 1分钟内不重新获取，保留缓存5分钟\nuseQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  staleTime: 60 * 1000,  // 1 minute\n  gcTime: 5 * 60 * 1000, // 5 minutes\n});\n```\n\n### 依赖关系追踪\n\n与 Pinia Colada 类似，chibivue-fetch 追踪每个查询条目被哪些组件使用：\n\n```ts\n// 组件挂载时追踪\nonMounted(() => {\n  queryCache.track(entry, currentInstance);\n});\n\n// 组件卸载时取消追踪\nonUnmounted(() => {\n  queryCache.untrack(entry, currentInstance);\n});\n```\n\n当没有依赖关系时，缓存会在 `gcTime` 后被垃圾回收．\n\n## SSR 支持\n\nchibivue-fetch 支持服务器端渲染（SSR）．\n\n### 服务器端：序列化状态\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createQueryCache, serializeQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // 为每个请求创建新实例\n  const queryCache = createQueryCache();\n  const app = createApp(App);\n  app.use(queryCache);\n\n  // 在服务器端预先获取数据\n  await queryCache.prefetchQuery(\n    [\"users\"],\n    ({ signal }) => fetch(\"http://api/users\", { signal }).then((r) => r.json()),\n  );\n\n  const html = await renderToString(app);\n\n  // 序列化缓存状态\n  const queryState = JSON.stringify(serializeQueryCache(queryCache));\n\n  return { html, queryState };\n}\n```\n\n### 序列化格式\n\n与 Pinia Colada 类似，我们使用相对时间戳进行序列化：\n\n```ts\n// UseQueryEntryNodeSerialized = [data, error, when (relative), meta]\n{\n  '[\"users\"]': [\n    [{ id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }], // data\n    null,                                                // error\n    0,                                                   // when (relative: now - fetchTime)\n    undefined                                            // meta\n  ]\n}\n```\n\n相对时间戳可以处理服务器和客户端之间的时间差异．\n\n### 嵌入 HTML\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.__QUERY_STATE__ = ${queryState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### 客户端：水合状态\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createQueryCache, hydrateQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nconst queryCache = createQueryCache();\nconst app = createApp(App);\napp.use(queryCache);\n\n// 使用服务器状态水合\nif (window.__QUERY_STATE__) {\n  hydrateQueryCache(queryCache, window.__QUERY_STATE__);\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\n在 SSR 中，与 Store 类似，您必须注意 **Cross-Request State Pollution**．\n在 `render()` 函数内调用 `createQueryCache()`，为每个请求创建新实例．\n\n</KawaikoNote>\n\n## 实用示例\n\n### 响应式 Query Key\n\n```ts\nimport { ref, computed } from \"chibivue\";\nimport { useQuery } from \"chibivue-fetch\";\n\nconst page = ref(1);\nconst filters = ref({ status: \"active\" });\n\nconst { data, isLoading } = useQuery({\n  // 函数格式用于动态键\n  key: () => [\"users\", { page: page.value, ...filters.value }],\n  query: ({ signal }) => fetchUsers(page.value, filters.value, signal),\n});\n\n// 当 page 或 filters 改变时自动重新获取\nfunction nextPage() {\n  page.value++;\n}\n```\n\n### 条件式查询\n\n```ts\nconst userId = ref<number | null>(null);\n\nconst { data: user } = useQuery({\n  key: () => [\"user\", userId.value],\n  query: ({ signal }) => fetchUser(userId.value!, signal),\n  // userId 为 null 时不执行查询\n  enabled: computed(() => userId.value !== null),\n});\n```\n\n### Mutation 后更新缓存\n\n```ts\nconst queryCache = getActiveQueryCache();\n\nconst { mutate: createUser } = useMutation({\n  mutation: (newUser) => api.createUser(newUser),\n  onSuccess: (createdUser) => {\n    // 方法1：使缓存失效并重新获取\n    queryCache.invalidateQueries([\"users\"]);\n\n    // 方法2：直接更新缓存（乐观更新）\n    const currentUsers = queryCache.getQueryData<User[]>([\"users\"]);\n    if (currentUsers) {\n      queryCache.setQueryData([\"users\"], [...currentUsers, createdUser]);\n    }\n  },\n});\n```\n\n### 错误处理和重试\n\n```ts\nconst { data, error, refresh } = useQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  retry: 3,        // 最多重试3次\n  retryDelay: 1000, // 1秒后重试\n});\n\n// 在组件中\nif (error.value) {\n  // 显示错误和重试按钮\n}\n```\n\n### 使用 AbortController 取消\n\n```ts\nconst { data } = useQuery({\n  key: [\"users\"],\n  query: async ({ signal }) => {\n    const response = await fetch(\"/api/users\", { signal });\n    if (!response.ok) throw new Error(\"Failed to fetch\");\n    return response.json();\n  },\n});\n```\n\n当查询被取消时（例如，当新请求开始时），`signal` 会被 abort．\n\n## 总结\n\nchibivue-fetch 实现包括以下要素：\n\n1. **QueryCache**：集中式缓存管理和依赖关系追踪\n2. **Data State 模式**：`pending | error | success` 的三状态模型\n3. **useQuery**：声明式数据获取 API\n4. **useMutation**：数据变更管理和生命周期回调\n5. **缓存策略**：通过 staleTime / gcTime 灵活控制\n6. **SSR 支持**：通过 `serializeQueryCache()` / `hydrateQueryCache()` 传输状态\n7. **响应式键**：动态查询键支持\n8. **错误处理**：自动重试和状态管理\n9. **AbortController**：请求取消支持\n\n通过最小化实现 Pinia Colada 的核心功能，您可以理解数据获取的工作方式．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/010-plugins/040-language-tools.md",
    "content": "# Language Tools\n\n## 什么是 Language Tools？\n\nLanguage Tools 为 `.vue` 单文件组件（SFCs）提供 IDE 支持．它们启用以下功能：\n\n- 语法高亮\n- 自动补全\n- 类型检查\n- 转到定义\n- 错误诊断\n\n在 Vue.js 生态系统中，[vuejs/language-tools](https://github.com/vuejs/language-tools) 提供此功能，它基于 [Volar.js](https://volarjs.dev/) 构建．在本章中，我们将使用 Volar.js 为 chibivue 实现最小化的语言工具．\n\n## 为什么需要 Language Tools？\n\nTypeScript 的语言服务只能理解 `.ts` 和 `.tsx` 文件．然而 `.vue` 文件包含多种语言混合在一起：\n\n```vue\n<template>\n  <div>{{ message }}</div>  <!-- HTML + 表达式 -->\n</template>\n\n<script setup lang=\"ts\">\nconst message = ref('Hello')  // TypeScript\n</script>\n\n<style scoped>\ndiv { color: red; }  /* CSS */\n</style>\n```\n\nLanguage Tools 的作用是将这种复合文件**转换**为 TypeScript 语言服务可以理解的格式．通过这种转换，在 `.vue` 文件中也可以使用 TypeScript 的所有功能（类型检查，自动补全，重构等）．\n\n## 架构概述\n\n语言工具由三个主要包组成：\n\n```txt\n@extensions/\n├── chibivue-language-core/     # 核心语言处理\n│   ├── parseSfc.ts             # SFC 解析器\n│   ├── virtualCode.ts          # 虚拟代码生成\n│   ├── languagePlugin.ts       # Volar.js 插件\n│   └── types.ts                # 类型定义\n├── chibivue-language-server/   # LSP 服务器\n│   └── server.ts               # 语言服务器协议服务器\n└── vscode-chibivue/            # VSCode 扩展\n    ├── extension.ts            # 扩展入口点\n    ├── syntaxes/               # TextMate 语法\n    └── language-configuration.json\n```\n\n### 数据流\n\n当你在编辑器中编辑 `.vue` 文件时，数据按以下流程处理：\n\n```txt\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                              VSCode                                         │\n│  ┌─────────────┐                                                            │\n│  │  App.vue    │  用户编辑 .vue 文件                                        │\n│  └──────┬──────┘                                                            │\n│         │                                                                   │\n│         ▼                                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  vscode-chibivue    │  VSCode 扩展检测文件变化                           │\n│  │  (Language Client)  │                                                    │\n│  └──────────┬──────────┘                                                    │\n└─────────────┼───────────────────────────────────────────────────────────────┘\n              │ LSP (Language Server Protocol)\n              ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│  chibivue-language-server                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  Language Server    │  接收 LSP 请求                                     │\n│  └──────────┬──────────┘                                                    │\n│             │                                                               │\n│             ▼                                                               │\n│  ┌─────────────────────┐    ┌─────────────────────┐                         │\n│  │  chibivue-language  │───▶│  Virtual Code       │                         │\n│  │  -core (Plugin)     │    │  (.vue → .ts 转换)  │                         │\n│  └─────────────────────┘    └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  TypeScript         │                         │\n│                             │  Language Service   │  类型检查、补全等       │\n│                             └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  Code Mappings      │  将结果映射回原位置     │\n│                             └─────────────────────┘                         │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n## 核心概念\n\n### 虚拟代码 (Virtual Code)\n\nLanguage Tools 的核心概念是**虚拟代码**．通过将 `.vue` 文件转换为 TypeScript，可以利用 TypeScript 语言服务的所有功能．\n\n#### 转换示例\n\n```vue\n<!-- 原始 .vue 文件 -->\n<template>\n  <div>{{ message }}</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n</script>\n```\n\n这会被转换为以下虚拟 TypeScript：\n\n```ts\n// 虚拟 TypeScript 代码\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n\n// 用于类型检查模板表达式的代码\n// 实际上不会执行，但允许 TypeScript 验证表达式类型\ndeclare const __VLS_template: () => void;\n(() => {\n  // 对应模板中的 {{ message }}\n  // TypeScript 验证 message 是否存在且类型正确\n  const __VLS_expr0 = (message);\n})();\n```\n\n通过这种转换：\n- 可以验证 `message` 的类型是 `Ref<string>`\n- 如果 `message` 未定义，会报告错误\n- 悬停在 `message` 上时会显示类型信息\n\n### 代码映射\n\n**代码映射**将虚拟代码中的位置链接回原始 `.vue` 文件的位置．\n\n```txt\n原始 .vue 文件                        虚拟 TypeScript\n─────────────────────────────────────────────────────────────\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'  ←──→  import { ref } from 'chibivue'\n                                      ↑\nconst message = ref('Hello')    ←──→  const message = ref('Hello')\n</script>                             ↑\n                                      │\n<template>                            │\n  <div>{{ message }}</div>      ←─────┼──→  const __VLS_expr0 = (message);\n</template>                           │\n                                      ↓\n                                映射将位置链接在一起\n```\n\n有了映射：\n- 虚拟代码中的错误 → 在原始 `.vue` 文件的正确位置显示\n- 执行\"转到定义\" → 将虚拟代码的位置转换为原始文件的位置\n\n## 实现\n\n### 类型定义\n\n首先，定义表示 SFC 结构的类型．\n\n```ts\n// types.ts\n\n/**\n * 表示 SFC 中的每个块（template、script、style）\n */\nexport interface SfcBlock {\n  /** 块类型（\"template\"、\"script\"、\"style\" 等） */\n  type: string;\n\n  /** 块内容（不包括标签的内部内容） */\n  content: string;\n\n  /** 位置信息（用于错误显示和映射） */\n  loc: {\n    start: { line: number; column: number; offset: number };\n    end: { line: number; column: number; offset: number };\n  };\n\n  /** 块属性（例如：lang=\"ts\"、scoped） */\n  attrs: Record<string, string | true>;\n\n  /** 语言指定（attrs.lang 的快捷方式） */\n  lang?: string;\n}\n\n/**\n * 表示解析后的整个 SFC\n */\nexport interface SfcDescriptor {\n  /** <template> 块 */\n  template: SfcBlock | null;\n\n  /** <script>（不带 setup）块 */\n  script: SfcBlock | null;\n\n  /** <script setup> 块 */\n  scriptSetup: SfcBlock | null;\n\n  /** <style> 块（可以有多个） */\n  styles: SfcBlock[];\n\n  /** 自定义块（例如：<docs>） */\n  customBlocks: SfcBlock[];\n}\n```\n\n### SFC 解析器\n\n解析 `.vue` 文件以生成 `SfcDescriptor`．\n\n::: tip\n在实际实现中，可以使用 chibivue 的 `@chibivue/compiler-sfc` 包中的 `parse` 函数．这里为了教育目的展示一个简化的解析器．\n:::\n\n```ts\n// parseSfc.ts\nimport type { SfcBlock, SfcDescriptor } from './types';\n\n/**\n * 解析 .vue 文件内容并返回 SfcDescriptor\n *\n * @param content - .vue 文件的内容\n * @param fileName - 文件名（用于错误消息）\n */\nexport function parseSfc(content: string, fileName: string): SfcDescriptor {\n  const descriptor: SfcDescriptor = {\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n    customBlocks: [],\n  };\n\n  // 匹配顶级块的正则表达式\n  // 检测 <tagName attrs>content</tagName> 格式\n  const blockRegex = /<(\\w+)([^>]*)>([\\s\\S]*?)<\\/\\1>/g;\n  let match: RegExpExecArray | null;\n\n  while ((match = blockRegex.exec(content)) !== null) {\n    const [fullMatch, tagName, attrsString, blockContent] = match;\n\n    // 计算块的起始位置\n    const startOffset = match.index + `<${tagName}${attrsString}>`.length;\n    const startPos = offsetToPosition(content, startOffset);\n\n    // 计算块的结束位置\n    const endOffset = startOffset + blockContent.length;\n    const endPos = offsetToPosition(content, endOffset);\n\n    // 解析属性（例如：'lang=\"ts\" scoped' → { lang: \"ts\", scoped: true }）\n    const attrs = parseAttrs(attrsString);\n\n    const block: SfcBlock = {\n      type: tagName,\n      content: blockContent,\n      loc: {\n        start: { ...startPos, offset: startOffset },\n        end: { ...endPos, offset: endOffset },\n      },\n      attrs,\n      lang: typeof attrs.lang === 'string' ? attrs.lang : undefined,\n    };\n\n    // 按块类型分类\n    switch (tagName) {\n      case 'template':\n        descriptor.template = block;\n        break;\n      case 'script':\n        // 根据是否有 setup 属性分类\n        if ('setup' in attrs) {\n          descriptor.scriptSetup = block;\n        } else {\n          descriptor.script = block;\n        }\n        break;\n      case 'style':\n        descriptor.styles.push(block);\n        break;\n      default:\n        descriptor.customBlocks.push(block);\n    }\n  }\n\n  return descriptor;\n}\n\n/**\n * 从偏移量（字符位置）计算行号和列号\n */\nfunction offsetToPosition(\n  content: string,\n  offset: number\n): { line: number; column: number } {\n  const lines = content.slice(0, offset).split('\\n');\n  return {\n    line: lines.length,\n    column: lines[lines.length - 1].length + 1,\n  };\n}\n\n/**\n * 解析属性字符串为对象\n * 示例：' lang=\"ts\" scoped' → { lang: \"ts\", scoped: true }\n */\nfunction parseAttrs(attrsString: string): Record<string, string | true> {\n  const attrs: Record<string, string | true> = {};\n  const attrRegex = /(\\w+)(?:=\"([^\"]*)\"|='([^']*)')?/g;\n  let attrMatch: RegExpExecArray | null;\n\n  while ((attrMatch = attrRegex.exec(attrsString)) !== null) {\n    const [, name, doubleQuoted, singleQuoted] = attrMatch;\n    attrs[name] = doubleQuoted ?? singleQuoted ?? true;\n  }\n\n  return attrs;\n}\n```\n\n### 虚拟代码生成\n\n实现 Volar.js 的 `VirtualCode` 接口．这是 Language Tools 的核心．\n\n```ts\n// virtualCode.ts\nimport type {\n  CodeMapping,\n  VirtualCode,\n} from '@volar/language-core';\nimport type * as ts from 'typescript';\nimport { parseSfc } from './parseSfc';\nimport type { SfcDescriptor } from './types';\n\n/**\n * 代码段：生成代码的一部分及其映射信息\n */\ntype CodeSegment = [\n  code: string,                           // 要生成的代码\n  sourceOffsetStart?: number,             // 源文件中的起始位置\n  sourceOffsetEnd?: number,               // 源文件中的结束位置\n  features?: { verification?: boolean },  // 映射功能设置\n];\n\n/**\n * 将 .vue 文件转换为虚拟 TypeScript 代码的类\n */\nexport class ChibivueVirtualCode implements VirtualCode {\n  id = 'root';\n  languageId = 'vue';\n  snapshot: ts.IScriptSnapshot;\n  mappings: CodeMapping[] = [];\n  embeddedCodes: VirtualCode[] = [];\n\n  private fileName: string;\n  private sfc: SfcDescriptor;\n\n  constructor(fileName: string, snapshot: ts.IScriptSnapshot) {\n    this.fileName = fileName;\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * 文件更新时调用\n   */\n  update(snapshot: ts.IScriptSnapshot): void {\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, this.fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * 生成虚拟代码的主处理\n   */\n  private generateVirtualCode(sourceContent: string): void {\n    const segments: CodeSegment[] = [];\n\n    // 1. 从 script/scriptSetup 生成代码\n    this.generateScriptCode(segments);\n\n    // 2. 生成模板类型检查代码\n    this.generateTemplateCode(segments);\n\n    // 3. 从段构建最终代码和映射\n    const { code, mappings } = this.buildCode(segments, sourceContent);\n\n    // 4. 注册为嵌入代码（TypeScript）\n    this.embeddedCodes = [\n      {\n        id: 'ts',\n        languageId: 'typescript',\n        snapshot: createScriptSnapshot(code),\n        mappings,\n        embeddedCodes: [],\n      },\n    ];\n  }\n\n  /**\n   * 从 script/scriptSetup 块生成 TypeScript 代码\n   */\n  private generateScriptCode(segments: CodeSegment[]): void {\n    const { script, scriptSetup } = this.sfc;\n\n    if (scriptSetup) {\n      // 原样输出 <script setup> 内容\n      // 添加映射信息（链接到源文件位置）\n      segments.push([\n        scriptSetup.content,\n        scriptSetup.loc.start.offset,\n        scriptSetup.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    } else if (script) {\n      // 原样输出 <script> 内容\n      segments.push([\n        script.content,\n        script.loc.start.offset,\n        script.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    }\n  }\n\n  /**\n   * 生成用于类型检查模板表达式的代码\n   */\n  private generateTemplateCode(segments: CodeSegment[]): void {\n    const { template } = this.sfc;\n    if (!template) return;\n\n    // 添加模板类型检查代码\n    segments.push(['\\n// Template type-checking\\n']);\n    segments.push(['declare const __VLS_template: () => void;\\n']);\n\n    // 检测 mustache 表达式 {{ expr }}\n    const mustacheRegex = /\\{\\{\\s*([\\s\\S]*?)\\s*\\}\\}/g;\n    let match: RegExpExecArray | null;\n    let exprIndex = 0;\n\n    while ((match = mustacheRegex.exec(template.content)) !== null) {\n      const expr = match[1];\n      // 计算表达式在源文件中的位置\n      const exprStartInTemplate = match.index + match[0].indexOf(expr);\n      const sourceStart = template.loc.start.offset + exprStartInTemplate;\n      const sourceEnd = sourceStart + expr.length;\n\n      // 生成验证表达式的代码\n      // (() => { const __VLS_expr0 = (message); })();\n      segments.push([`(() => {\\n  const __VLS_expr${exprIndex} = (`]);\n      segments.push([\n        expr,\n        sourceStart,\n        sourceEnd,\n        { verification: true },\n      ]);\n      segments.push([');\\n})();\\n']);\n\n      exprIndex++;\n    }\n  }\n\n  /**\n   * 从段构建最终代码和映射\n   */\n  private buildCode(\n    segments: CodeSegment[],\n    sourceContent: string\n  ): { code: string; mappings: CodeMapping[] } {\n    let code = '';\n    const mappings: CodeMapping[] = [];\n\n    for (const segment of segments) {\n      const [text, sourceStart, sourceEnd, features] = segment;\n\n      if (sourceStart !== undefined && sourceEnd !== undefined) {\n        // 存在映射信息时记录\n        mappings.push({\n          sourceOffsets: [sourceStart],\n          generatedOffsets: [code.length],\n          lengths: [sourceEnd - sourceStart],\n          data: {\n            verification: features?.verification ?? false,\n            completion: true,\n            semantic: true,\n            navigation: true,\n            structure: true,\n            format: false,\n          },\n        });\n      }\n\n      code += text;\n    }\n\n    return { code, mappings };\n  }\n}\n\n/**\n * 创建 TypeScript 脚本快照\n */\nfunction createScriptSnapshot(content: string): ts.IScriptSnapshot {\n  return {\n    getText: (start, end) => content.slice(start, end),\n    getLength: () => content.length,\n    getChangeRange: () => undefined,\n  };\n}\n```\n\n### 语言插件\n\n实现告诉 Volar.js 如何处理 `.vue` 文件的插件．\n\n```ts\n// languagePlugin.ts\nimport type { LanguagePlugin } from '@volar/language-core';\nimport { ChibivueVirtualCode } from './virtualCode';\n\n/**\n * 为 Volar.js 创建语言插件\n *\n * 此插件负责：\n * 1. 识别 .vue 文件\n * 2. 从 .vue 文件生成虚拟代码\n * 3. 向 TypeScript 语言服务提供虚拟代码\n */\nexport function createChibivueLanguagePlugin(): LanguagePlugin<\n  string,\n  ChibivueVirtualCode\n> {\n  return {\n    /**\n     * 从文件扩展名判断语言 ID\n     * 对于 .vue 文件返回 \"vue\"\n     */\n    getLanguageId(scriptId: string): string | undefined {\n      if (scriptId.endsWith('.vue')) {\n        return 'vue';\n      }\n      return undefined;\n    },\n\n    /**\n     * 创建新的虚拟代码\n     * 首次打开文件时调用\n     */\n    createVirtualCode(scriptId, languageId, snapshot) {\n      if (languageId === 'vue') {\n        return new ChibivueVirtualCode(scriptId, snapshot);\n      }\n      return undefined;\n    },\n\n    /**\n     * 更新现有的虚拟代码\n     * 编辑文件时调用\n     */\n    updateVirtualCode(_scriptId, virtualCode, snapshot) {\n      virtualCode.update(snapshot);\n      return virtualCode;\n    },\n\n    /**\n     * TypeScript 特定设置\n     */\n    typescript: {\n      /**\n       * 使 TypeScript 识别 .vue 文件的设置\n       *\n       * - extension: 目标文件扩展名\n       * - isMixedContent: 表示包含多种语言\n       * - scriptKind: TypeScript 的 ScriptKind\n       *   - 7 = Deferred（延迟评估，使用虚拟代码）\n       */\n      extraFileExtensions: [\n        { extension: 'vue', isMixedContent: true, scriptKind: 7 },\n      ],\n\n      /**\n       * 从虚拟代码获取要传递给 TypeScript 的脚本\n       *\n       * @returns\n       *   - code: 嵌入的 TypeScript 代码\n       *   - extension: \".ts\"（作为 TypeScript 处理）\n       *   - scriptKind: 3 = TS（普通 TypeScript）\n       */\n      getServiceScript(rootVirtualCode) {\n        for (const code of rootVirtualCode.embeddedCodes) {\n          if (code.id === 'ts') {\n            return {\n              code,\n              extension: '.ts',\n              scriptKind: 3, // ts.ScriptKind.TS\n            };\n          }\n        }\n        return undefined;\n      },\n    },\n  };\n}\n```\n\n### 语言服务器\n\nLSP（语言服务器协议）服务器连接编辑器和语言功能．\n\n```ts\n// server.ts\nimport {\n  createConnection,\n  createServer,\n  createSimpleProjectProviderFactory,\n  loadTsdkByPath,\n} from '@volar/language-server/node';\nimport { create as createTypeScriptServices } from 'volar-service-typescript';\nimport { createChibivueLanguagePlugin } from '@chibivue/language-core';\n\n/**\n * 关于 LSP（语言服务器协议）\n *\n * LSP 是将编辑器与语言功能分离的协议．\n *\n * ┌──────────┐                        ┌──────────────────┐\n * │  VSCode  │ ◄───── LSP 通信 ─────► │  Language Server │\n * │  Neovim  │    (JSON-RPC over      │  （此文件）      │\n * │  Emacs   │     stdio/IPC)         │                  │\n * └──────────┘                        └──────────────────┘\n *\n * 主要 LSP 请求：\n * - textDocument/completion: 获取自动补全候选\n * - textDocument/hover: 获取悬停信息\n * - textDocument/definition: 转到定义\n * - textDocument/references: 查找引用\n * - textDocument/rename: 重命名符号\n * - textDocument/diagnostics: 错误诊断\n */\n\n// 创建 LSP 连接（通过 stdin/stdout 或 IPC 通信）\nconst connection = createConnection();\n\n// 创建 Volar 语言服务器\nconst server = createServer(connection);\n\n// 开始监听连接\nconnection.listen();\n\n/**\n * 初始化请求的处理程序\n * 客户端（编辑器）连接时调用\n */\nconnection.onInitialize((params) => {\n  // 获取 TypeScript SDK 路径（从客户端传递）\n  const tsdk = params.initializationOptions?.typescript?.tsdk;\n\n  // 加载 TypeScript 模块\n  const ts = tsdk\n    ? loadTsdkByPath(tsdk, params.locale)\n    : require('typescript');\n\n  // 创建 chibivue 语言插件\n  const chibivuePlugin = createChibivueLanguagePlugin();\n\n  // 初始化服务器并注册功能\n  return server.initialize(\n    params,\n    // 项目管理设置（tsconfig.json 检测等）\n    createSimpleProjectProviderFactory(),\n    {\n      /**\n       * 返回语言插件\n       * 负责从 .vue 文件生成虚拟代码\n       */\n      getLanguagePlugins() {\n        return [chibivuePlugin];\n      },\n\n      /**\n       * 返回服务插件\n       * 提供 TypeScript 语言功能（补全、诊断等）\n       */\n      getServicePlugins() {\n        return [...createTypeScriptServices(ts)];\n      },\n    }\n  );\n});\n\n/**\n * 初始化完成的处理程序\n */\nconnection.onInitialized(() => {\n  // 根据需要进行额外设置\n});\n```\n\n### VSCode 扩展\n\n实现将 VSCode 连接到语言服务器的扩展．\n\n```ts\n// extension.ts\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport {\n  LanguageClient,\n  LanguageClientOptions,\n  ServerOptions,\n  TransportKind,\n} from 'vscode-languageclient/node';\n\nlet client: LanguageClient | undefined;\n\n/**\n * 扩展激活\n * 打开 .vue 文件时自动调用\n */\nexport async function activate(context: vscode.ExtensionContext) {\n  // 解析语言服务器路径\n  const serverPath = context.asAbsolutePath(\n    path.join('dist', 'server.js')\n  );\n\n  // 服务器启动选项\n  const serverOptions: ServerOptions = {\n    run: {\n      module: serverPath,\n      transport: TransportKind.ipc, // 通过 IPC 通信\n    },\n    debug: {\n      module: serverPath,\n      transport: TransportKind.ipc,\n      options: { execArgv: ['--nolazy', '--inspect=6009'] },\n    },\n  };\n\n  // 客户端选项\n  const clientOptions: LanguageClientOptions = {\n    // 处理哪些文件\n    documentSelector: [{ scheme: 'file', language: 'vue' }],\n\n    // 初始化时传递给服务器的选项\n    initializationOptions: {\n      typescript: {\n        // 使用 VSCode 内置的 TypeScript SDK\n        tsdk: path.join(\n          vscode.env.appRoot,\n          'extensions/node_modules/typescript/lib'\n        ),\n      },\n    },\n  };\n\n  // 创建 Language Client\n  client = new LanguageClient(\n    'chibivue',                    // 客户端 ID\n    'Chibivue Language Server',   // 显示名称\n    serverOptions,\n    clientOptions\n  );\n\n  // 启动语言服务器\n  await client.start();\n\n  // 扩展停用时清理\n  context.subscriptions.push({\n    dispose: () => client?.stop(),\n  });\n}\n\n/**\n * 扩展停用\n */\nexport function deactivate(): Thenable<void> | undefined {\n  return client?.stop();\n}\n```\n\n### 语法高亮（TextMate 语法）\n\n语法高亮使用 TextMate 语法定义．这使用 VSCode 的内置功能，不涉及语言服务器．\n\n```json\n// syntaxes/vue.tmLanguage.json\n{\n  \"name\": \"Vue\",\n  \"scopeName\": \"source.vue\",\n  \"patterns\": [\n    { \"include\": \"#template\" },\n    { \"include\": \"#script\" },\n    { \"include\": \"#style\" }\n  ],\n  \"repository\": {\n    \"template\": {\n      \"begin\": \"(<)(template)\",\n      \"end\": \"(</)(template)(>)\",\n      \"patterns\": [{ \"include\": \"text.html.basic\" }]\n    },\n    \"script\": {\n      \"begin\": \"(<)(script)\",\n      \"end\": \"(</)(script)(>)\",\n      \"patterns\": [{ \"include\": \"source.ts\" }]\n    },\n    \"style\": {\n      \"begin\": \"(<)(style)\",\n      \"end\": \"(</)(style)(>)\",\n      \"patterns\": [{ \"include\": \"source.css\" }]\n    }\n  }\n}\n```\n\n## 支持的功能\n\n| 功能       | 状态   | 描述                           |\n| ---------- | ------ | ------------------------------ |\n| 语法高亮   | 已支持 | 通过 TextMate 语法进行颜色编码 |\n| 自动补全   | 已支持 | 变量，函数，属性补全           |\n| 类型检查   | 已支持 | 通过 TypeScript 检测类型错误   |\n| 转到定义   | 已支持 | 跳转到变量/函数定义            |\n| 错误诊断   | 已支持 | 显示语法和类型错误             |\n| 重命名符号 | 已支持 | 批量重命名变量等               |\n| 悬停信息   | 已支持 | 显示光标位置的类型信息         |\n\n## 总结\n\nLanguage Tools 通过将 `.vue` 文件转换为虚拟 TypeScript 代码，使 SFC 中可以使用 TypeScript 的所有功能．\n\n**主要组件：**\n\n1. **SFC 解析器** - 将 `.vue` 文件分解为 template，script 和 style 块\n2. **虚拟代码生成** - 将 SFC 转换为带有代码映射的 TypeScript\n3. **语言插件** - 实现 Volar.js 接口以提供虚拟代码\n4. **语言服务器** - 通过 LSP 与编辑器通信\n5. **VSCode 扩展** - 将 VSCode 连接到语言服务器\n\n此实现是用于教育目的的最小实现．生产环境使用的 [vuejs/language-tools](https://github.com/vuejs/language-tools) 添加了许多高级功能：\n\n- 模板指令（`v-if`，`v-for` 等）的类型检查\n- 组件 props 类型验证\n- `<style scoped>` 选择器补全\n- `<template>` 中的 HTML 补全\n- 宏支持（`defineProps`，`defineEmits`）\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/020-ssr/010-create-ssr-app.md",
    "content": "# 服务端渲染 (SSR)\n\n## 什么是 SSR\n\n服务端渲染（SSR）是一种在服务器上将 Vue.js 应用程序渲染为 HTML 字符串并发送到客户端的技术．这提供了以下优势：\n\n1. **改善 SEO**：搜索引擎爬虫可以获取完整的内容\n2. **更快的首次显示**：浏览器无需等待 JavaScript 执行即可显示 HTML\n3. **性能改善**：在低速设备或网络环境下特别有效\n\n## 包结构\n\nchibivue 的 SSR 实现在 `@chibivue/server-renderer` 包中提供．\n\n```\npackages/server-renderer/src/\n├── index.ts\n├── renderToString.ts      # 主入口\n├── render.ts              # VNode 渲染\n└── helpers/\n    ├── ssrRenderAttrs.ts  # 属性渲染\n    └── ssrUtils.ts        # 工具函数\n```\n\n## 类型定义\n\n### SSRBuffer\n\n在 SSR 中，我们使用名为 `SSRBuffer` 的数据结构来高效构建渲染结果．\n\n```ts\n// packages/server-renderer/src/render.ts\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\n```\n\n缓冲区可以包含：\n- **字符串**：HTML 的一部分\n- **嵌套缓冲区**：子组件的结果\n- **Promise**：异步组件的结果\n\n### SSRContext\n\n保存 SSR 期间的上下文信息．\n\n```ts\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  __watcherHandles?: (() => void)[];\n};\n```\n\n## renderToString 实现\n\n### 主入口\n\n```ts\n// packages/server-renderer/src/renderToString.ts\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // 直接传入 VNode 时，用包装组件包裹\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // App 实例的情况\n  const app = input;\n  const vnode = createVNode(app._component, app._props);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  // 清理 watcher\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n```\n\n### 缓冲区展开\n\n递归展开嵌套的缓冲区和 Promise．\n\n```ts\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  // 如果没有异步元素，同步处理\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    // Promise 的情况下等待解析\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    // 嵌套缓冲区递归处理\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n```\n\n## createBuffer 实现\n\n用于高效构建缓冲区的工厂函数．\n\n```ts\n// packages/server-renderer/src/render.ts\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        // 连续字符串自动拼接优化\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      // 如果有 Promise 或异步缓冲区，设置标志\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n```\n\n要点：\n1. 连续字符串自动拼接（内存效率）\n2. `appendable` 标志跟踪是否可以拼接\n3. 如果有异步元素，设置 `hasAsync` 标志\n\n## 组件渲染\n\n### renderComponentVNode\n\n```ts\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  // 创建组件实例\n  const instance = (vnode.component = createComponentInstance(\n    vnode,\n    parentComponent,\n    null,\n  ));\n\n  // 执行 setup\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  // 异步 setup 的情况下返回 Promise\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() =>\n      renderComponentSubTree(instance),\n    );\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n```\n\n### renderComponentSubTree\n\n```ts\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    // 函数式组件\n    const root = comp(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    // 有 render 函数的组件\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n```\n\n## VNode 渲染\n\n### renderVNode\n\n根据各种 VNode 类型进行渲染．\n\n```ts\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  // 指令的 SSR 支持\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(\n        children\n          ? `<!--${escapeHtmlComment(children as string)}-->`\n          : `<!---->`,\n      );\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      } else if (shapeFlag & ShapeFlags.TELEPORT) {\n        renderTeleportVNode(push, vnode, parentComponent);\n      }\n  }\n}\n```\n\n### renderElementVNode\n\n将 HTML 元素渲染为字符串．\n\n```ts\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  // 渲染属性\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  // void 标签没有闭合标签\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      // 处理特殊属性\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n```\n\n### renderVNodeChildren\n\n按顺序渲染子元素．\n\n```ts\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n```\n\n### renderTeleportVNode\n\nTeleport 组件的 SSR 支持．\n\n```ts\nfunction renderTeleportVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const target = vnode.props && vnode.props.to;\n  const disabled = vnode.props && vnode.props.disabled;\n\n  if (!target) {\n    if (!disabled) {\n      console.warn(`Teleport is missing target prop.`);\n    }\n    return;\n  }\n\n  if (!isString(target)) {\n    console.warn(`Teleport target must be a query selector string.`);\n    return;\n  }\n\n  // disabled 的情况下内联渲染\n  if (disabled) {\n    renderVNodeChildren(push, vnode.children as VNodeArrayChildren, parentComponent);\n  } else {\n    // enabled 的情况下插入占位符注释\n    push(`<!--teleport start-->`);\n    push(`<!--teleport end-->`);\n  }\n}\n```\n\n## 属性渲染\n\n### ssrRenderAttrs\n\n```ts\n// packages/server-renderer/src/helpers/ssrRenderAttrs.ts\nexport function ssrRenderAttrs(\n  props: Record<string, unknown>,\n  tag?: string,\n): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (\n      ssrIsIgnoredKey(key) ||\n      isOn(key) ||\n      (tag === \"textarea\" && key === \"value\")\n    ) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return (\n    key === \"key\" ||\n    key === \"ref\" ||\n    key === \"innerHTML\" ||\n    key === \"textContent\"\n  );\n}\n```\n\n### ssrRenderDynamicAttr\n\n渲染动态属性．\n\n```ts\nexport function ssrRenderDynamicAttr(\n  key: string,\n  value: unknown,\n  tag?: string,\n): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n\n  // 自定义元素或 SVG 保持原样，否则转换\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag))\n      ? key\n      : propsToAttrMap[key] || key.toLowerCase();\n\n  // 处理布尔属性\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\"\n      ? ` ${attrKey}`\n      : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(\n      `[@chibivue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`,\n    );\n    return \"\";\n  }\n}\n```\n\n### 渲染 class 和 style\n\n```ts\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(\n  styles: Record<string, string | number> | null,\n): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n```\n\n## 指令的 SSR 支持\n\n```ts\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const { dir: { getSSRProps } } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n```\n\n如果指令实现了 `getSSRProps`，其结果将合并到 props 中．\n\n## 转义处理\n\n防止 XSS 的 HTML 转义．\n\n```ts\n// packages/server-renderer/src/helpers/ssrUtils.ts\nconst escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n```\n\n## 使用示例\n\n```ts\nimport { createApp } from \"@chibivue/runtime-dom\";\nimport { renderToString } from \"@chibivue/server-renderer\";\n\nconst App = {\n  setup() {\n    return { message: \"Hello SSR!\" };\n  },\n  template: `<div>{{ message }}</div>`,\n};\n\nconst app = createApp(App);\n\n// 服务端渲染\nconst html = await renderToString(app);\nconsole.log(html); // <div>Hello SSR!</div>\n```\n\n## 处理流程\n\n```\nrenderToString(app)\n  ↓\ncreateVNode(app._component, app._props)\n  ↓\nrenderComponentVNode(vnode)\n  ├── createComponentInstance()\n  ├── setupComponent()\n  └── renderComponentSubTree()\n      ├── createBuffer()\n      ├── instance.render() 或 comp()\n      └── renderVNode(push, root, instance)\n          ├── Text → escapeHtml(children)\n          ├── Comment → <!--...-->\n          ├── Fragment → <!--[--> ... <!--]-->\n          ├── Element → renderElementVNode()\n          │   ├── <tag + ssrRenderAttrs(props) + >\n          │   ├── children 处理\n          │   └── </tag>\n          └── Component → renderComponentVNode()（递归）\n  ↓\nunrollBuffer(buffer)\n  ↓\nHTML 字符串\n```\n\n## 总结\n\nchibivue 的 SSR 实现由以下元素组成：\n\n1. **SSRBuffer**：用于高效字符串构建的缓冲系统（字符串自动拼接，异步支持）\n2. **renderComponentVNode**：将组件 VNode 转换为 HTML（异步 setup 支持）\n3. **renderVNode**：根据各种 VNode 类型进行渲染分支\n4. **renderElementVNode**：HTML 元素的字符串化（void 标签，特殊属性支持）\n5. **ssrRenderAttrs**：属性渲染（class/style 标准化，布尔属性，安全检查）\n6. **转义处理**：防止 XSS 的 HTML 转义\n7. **指令支持**：通过 `getSSRProps` 在 SSR 时进行属性注入\n\n在下一节中，我们将学习 hydration，它在客户端\"恢复\"SSR 生成的 HTML．\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/010_ssr)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/020-ssr/020-hydration.md",
    "content": "# Hydration（水合）\n\n## 什么是 Hydration？\n\n在上一章中，我们学习了如何使用 `renderToString` 将 Vue 组件渲染为 HTML 字符串．但是，SSR 生成的 HTML 只是静态标记——事件处理器和响应式都不起作用．\n\nHydration（水合）是将服务器生成的 HTML \"激活\"为客户端 Vue 应用程序的过程．\n\n<KawaikoNote variant=\"question\" title=\"为什么叫'水合'？\">\n\n\"Hydration\"（水合）这个名字来自于给静态 HTML \"注入生命\"的形象．\n就像干枯的植物浇水后会变得生机勃勃一样，我们向静态 HTML 注入事件处理器和响应式．\n\n</KawaikoNote>\n\n## 与普通挂载的区别\n\n### 普通 `createApp`\n\n```\n1. 生成 VNode\n2. 创建新的 DOM 元素\n3. 将 DOM 插入容器\n```\n\n### `createSSRApp`（Hydration）\n\n```\n1. 生成 VNode\n2. 遍历已存在的 DOM 元素\n3. 将 VNode 与 DOM 元素关联\n4. 附加事件处理器\n```\n\n<KawaikoNote variant=\"funny\" title=\"Hydration 的本质\">\n\nHydration 可以理解为\"不创建 DOM 的渲染\"．\n由于 DOM 已经存在，我们只需要将它与 VNode 关联起来．\n\n</KawaikoNote>\n\n## 类型定义\n\n### HydrateOptions\n\n定义 Hydration 所需的选项．\n\n```ts\n// runtime-core/hydration.ts\nexport interface HydrateOptions {\n  patchProp: (el: Element, key: string, prevValue: any, nextValue: any) => void;\n  nextSibling: (node: Node) => Node | null;\n}\n```\n\n- `patchProp`：将属性（特别是事件处理器）附加到 DOM 元素的函数\n- `nextSibling`：遍历 DOM 树的函数\n\n## createHydrationRenderer 实现\n\n### 基本结构\n\n```ts\n// runtime-core/hydration.ts\nexport function createHydrationRenderer(options: HydrateOptions) {\n  const { patchProp, nextSibling } = options;\n\n  function hydrate(vnode: VNode, container: Element): void {\n    const node = container.firstChild;\n    if (node) {\n      hydrateNode(node, vnode, null);\n    }\n  }\n\n  // ... 其他函数\n\n  return { hydrate };\n}\n```\n\n`hydrate` 函数从容器的第一个子节点开始，并行遍历 VNode 树和 DOM 树．\n\n### hydrateNode - 根据节点类型分支\n\n```ts\nfunction hydrateNode(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  const { type, shapeFlag } = vnode;\n\n  // 重要：将 VNode 与 DOM 元素关联\n  vnode.el = node;\n\n  if (type === Text) {\n    // 文本节点：返回下一个兄弟节点\n    return nextSibling(node);\n  } else if (type === Comment) {\n    // 注释节点：返回下一个兄弟节点\n    return nextSibling(node);\n  } else if (type === Fragment) {\n    // Fragment：特殊处理\n    return hydrateFragment(node, vnode, parentComponent);\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    // HTML 元素：也处理子元素\n    return hydrateElement(node as Element, vnode, parentComponent);\n  }\n\n  return nextSibling(node);\n}\n```\n\n要点：\n- `vnode.el = node` 是最重要的操作．这使后续更新能够引用正确的 DOM 元素\n- 每个函数返回\"下一个要处理的 DOM 节点\"\n\n### hydrateElement - HTML 元素的水合\n\n```ts\nfunction hydrateElement(\n  el: Element,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  vnode.el = el;\n\n  const { props, children, shapeFlag } = vnode;\n\n  // 附加事件处理器\n  if (props) {\n    for (const key in props) {\n      if (key.startsWith(\"on\") && typeof props[key] === \"function\") {\n        patchProp(el, key, null, props[key]);\n      }\n    }\n  }\n\n  // 水合子元素\n  if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n    hydrateChildren(el.firstChild, children as VNode[], parentComponent);\n  }\n\n  return nextSibling(el);\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"只附加事件处理器\">\n\nHydration 时我们只处理事件处理器（以 `on` 开头的 props）．\n像 `class` 或 `style` 这样的属性已经包含在 SSR 的 HTML 中，所以不需要附加．\n\n</KawaikoNote>\n\n### hydrateChildren - 处理子元素\n\n```ts\nfunction hydrateChildren(\n  node: Node | null,\n  children: VNode[],\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  for (let i = 0; i < children.length; i++) {\n    const child = normalizeVNode(children[i]);\n    if (node) {\n      node = hydrateNode(node, child, parentComponent);\n    }\n  }\n  return node;\n}\n```\n\n按顺序处理 VNode 子元素和 DOM 子节点．每个 `hydrateNode` 返回下一个兄弟节点，用于继续遍历．\n\n### hydrateFragment - Fragment 处理\n\n在 SSR 中，Fragment 被包装在 `<!--[-->` 和 `<!--]-->` 注释节点中渲染．\n\n```ts\nfunction hydrateFragment(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  // 将开始注释（<!--[-->）设置到 el\n  vnode.el = node;\n\n  // 子元素从开始注释之后开始\n  let current = nextSibling(node);\n  const children = vnode.children as VNode[];\n\n  if (children && children.length > 0) {\n    current = hydrateChildren(current, children, parentComponent);\n  }\n\n  // 将结束注释（<!--]-->）设置到 anchor\n  vnode.anchor = current;\n  return current ? nextSibling(current) : null;\n}\n```\n\n```html\n<!-- SSR 输出示例 -->\n<!--[-->\n<p>Item 1</p>\n<p>Item 2</p>\n<p>Item 3</p>\n<!--]-->\n```\n\n## createSSRApp 实现\n\n`createSSRApp` 与普通的 `createApp` 几乎相同，但在挂载时执行 Hydration．\n\n```ts\n// runtime-dom/index.ts\n\n// 创建 Hydration 渲染器\nconst { hydrate: hydrateVNode } = createHydrationRenderer({\n  patchProp,\n  nextSibling: nodeOps.nextSibling,\n});\n\nexport const createSSRApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n\n    // 检查容器是否有 SSR 内容\n    if (container.hasChildNodes()) {\n      // 执行 Hydration\n      const proxy = mount(container, true /* isHydrate */);\n      return proxy;\n    } else {\n      // 如果没有 SSR 内容，普通挂载\n      mount(container);\n    }\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n```\n\n## 处理流程\n\n```\n[服务器端]\nrenderToString(app)\n  ↓\n<div id=\"app\">\n  <button>Count: 0</button>\n</div>\n\n[客户端]\ncreateSSRApp(App).mount('#app')\n  ↓\ncontainer.hasChildNodes() → true\n  ↓\nhydrate(vnode, container)\n  ↓\nhydrateNode(button, vnode)\n  ├── vnode.el = button  ← 将 VNode 与 DOM 关联\n  └── patchProp(button, 'onClick', null, handler)  ← 附加事件\n  ↓\n点击按钮触发响应式\n```\n\n## 使用示例\n\n### 服务器端\n\n```ts\n// server.ts\nimport { createApp } from '@chibivue/runtime-dom'\nimport { renderToString } from '@chibivue/server-renderer'\nimport App from './App.vue'\n\nconst app = createApp(App)\nconst html = await renderToString(app)\n\n// 将 HTML 发送给客户端\nres.send(`\n  <!DOCTYPE html>\n  <html>\n    <body>\n      <div id=\"app\">${html}</div>\n      <script src=\"/client.js\"></script>\n    </body>\n  </html>\n`)\n```\n\n### 客户端\n\n```ts\n// client.ts\nimport { createSSRApp } from '@chibivue/runtime-dom'\nimport App from './App.vue'\n\n// 使用 createSSRApp（不是 createApp）\nconst app = createSSRApp(App)\napp.mount('#app')\n```\n\n### App 组件\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { ref } from '@chibivue/runtime-core'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n\n<template>\n  <button @click=\"increment\">Count: {{ count }}</button>\n</template>\n```\n\n## Hydration 不匹配\n\n在 Hydration 期间，SSR 生成的 HTML 必须与客户端生成的 VNode 匹配．如果不匹配，就会发生\"Hydration 不匹配\"．\n\n### 常见原因\n\n1. **日期/随机数**：`new Date()` 或 `Math.random()` 在服务器和客户端产生不同的值\n2. **浏览器特定的 API**：`window` 或 `localStorage` 在服务器上不存在\n3. **条件分支**：服务器和客户端走不同的代码路径\n\n### 解决方案\n\n```vue\n<script setup>\nimport { ref, onMounted } from '@chibivue/runtime-core'\n\n// 服务器和客户端相同的初始值\nconst clientOnly = ref(false)\n\n// 仅在客户端更新\nonMounted(() => {\n  clientOnly.value = true\n})\n</script>\n\n<template>\n  <div v-if=\"clientOnly\">\n    This content is only shown on client\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"注意不匹配！\">\n\n当 Hydration 不匹配发生时，Vue 会发出警告，最坏的情况下 DOM 可能会损坏．\n注意确保服务器和客户端产生相同的输出．\n\n</KawaikoNote>\n\n## 未来扩展\n\n当前实现是最小化的，但 Vue 本身有以下功能：\n\n1. **Hydration 不匹配检测**：在开发模式下检测服务器/客户端不一致\n2. **部分 Hydration**：只水合必要的部分（性能优化）\n3. **使用 PatchFlags 优化**：跳过静态节点的 Hydration\n4. **异步组件 Hydration**：与 `Suspense` 集成\n\n<KawaikoNote variant=\"surprise\" title=\"Hydration 完成！\">\n\n现在我们拥有了 SSR 的所有部分．\n通过使用 `renderToString` 进行服务器端渲染和\n`createSSRApp` 进行 Hydration，\n我们可以实现完整的 SSR 应用程序．\n\n</KawaikoNote>\n\n## 总结\n\nHydration 实现由以下部分组成：\n\n1. **createHydrationRenderer**：创建用于 Hydration 的渲染器\n2. **hydrateNode**：根据 VNode 类型分支处理\n3. **hydrateElement**：HTML 元素和事件处理器附加\n4. **hydrateChildren**：递归处理子元素\n5. **hydrateFragment**：处理 Fragment（被注释节点包围的区域）\n6. **createSSRApp**：支持 Hydration 的应用程序工厂\n\nHydration 的本质是\"将 VNode 与已存在的 DOM 关联而不重新创建\"．这使得 SSR 的快速初始显示和 SPA 的丰富交互性得以兼顾．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/020-ssr/030-compiler-ssr.md",
    "content": "# Compiler SSR\n\n## 什么是 SSR 编译器？\n\nSSR 编译器（`@chibivue/compiler-ssr`）是一个将模板编译为 SSR 优化代码的包．\n\n普通的客户端编译会输出生成 VNode 的代码，而 SSR 编译器直接输出生成 HTML 字符串的代码．这提高了服务器端的渲染效率．\n\n<KawaikoNote variant=\"base\" title=\"客户端与 SSR 的区别\">\n\n在客户端：\n```js\n// 返回 VNode\nreturn _createElementVNode(\"div\", { class: \"hello\" }, \"Hello\")\n```\n\n在 SSR 中：\n```js\n// 直接 push HTML 字符串\n_push(`<div class=\"hello\">Hello</div>`)\n```\n\nSSR 不经过 VNode 直接生成字符串，所以效率更高！\n\n</KawaikoNote>\n\n## 包结构\n\n```\npackages/compiler-ssr/src/\n├── index.ts                    # 主入口点\n├── runtimeHelpers.ts           # SSR 辅助函数定义\n├── ssrCodegenTransform.ts      # SSR 代码生成转换\n└── transforms/\n    ├── ssrTransformElement.ts   # 元素转换\n    ├── ssrTransformComponent.ts # 组件转换\n    ├── ssrVIf.ts               # v-if 转换\n    └── ssrVFor.ts              # v-for 转换\n```\n\n## 编译流程\n\nSSR 编译按以下步骤进行：\n\n1. **解析**：将模板转换为 AST（使用 `@chibivue/compiler-dom` 的 `parse`）\n2. **转换**：应用 SSR NodeTransform\n3. **SSR Codegen Transform**：将 AST 转换为 SSR 代码生成节点\n4. **代码生成**：生成最终的 JavaScript 代码\n\n```ts\n// packages/compiler-ssr/src/index.ts\nexport function compile(source: string | RootNode, options: CompilerOptions = {}): CodegenResult {\n  const ast = typeof source === \"string\" ? baseParse(source, options) : source;\n\n  transform(ast, {\n    ...options,\n    nodeTransforms: [\n      ssrTransformIf,\n      ssrTransformFor,\n      transformExpression,\n      ssrTransformElement,\n      ssrTransformComponent,\n      ...(options.nodeTransforms || []),\n    ],\n  });\n\n  // 将模板 AST 转换为 SSR codegen AST\n  ssrCodegenTransform(ast, options);\n\n  return generate(ast, options);\n}\n```\n\n## SSR Transform Context\n\nSSR 转换中使用的上下文．\n\n```ts\n// packages/compiler-ssr/src/ssrCodegenTransform.ts\nexport interface SSRTransformContext {\n  root: RootNode;\n  options: CompilerOptions;\n  body: (JSChildNode | IfStatement)[];\n  helpers: Set<symbol>;\n  onError: (error: Error) => void;\n  helper<T extends symbol>(name: T): T;\n  pushStringPart(part: TemplateLiteral[\"elements\"][0]): void;\n  pushStatement(statement: IfStatement | CallExpression): void;\n}\n```\n\n### pushStringPart\n\n将字符串部分添加到缓冲区．连续的字符串会自动合并．\n\n```ts\npushStringPart(part) {\n  if (!currentString) {\n    const currentCall = createCallExpression(`_push`);\n    body.push(currentCall);\n    currentString = createTemplateLiteral([]);\n    currentCall.arguments.push(currentString);\n  }\n  const bufferedElements = currentString.elements;\n  const lastItem = bufferedElements[bufferedElements.length - 1];\n  if (isString(part) && isString(lastItem)) {\n    // 合并连续的字符串\n    bufferedElements[bufferedElements.length - 1] += part;\n  } else {\n    bufferedElements.push(part);\n  }\n}\n```\n\n### pushStatement\n\n将控制流语句（if/for）添加到缓冲区．\n\n```ts\npushStatement(statement) {\n  // 关闭当前字符串缓冲区\n  currentString = null;\n  body.push(statement);\n}\n```\n\n## 元素转换\n\n### ssrTransformElement\n\n将 HTML 元素转换为 SSR 代码．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformElement.ts\nexport const ssrTransformElement: NodeTransform = (node, context) => {\n  if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.ELEMENT) {\n    return;\n  }\n\n  return function ssrPostTransformElement() {\n    const openTag: TemplateLiteral[\"elements\"] = [`<${node.tag}`];\n\n    // 处理属性\n    for (const prop of node.props) {\n      if (prop.type === NodeTypes.ATTRIBUTE) {\n        openTag.push(` ${prop.name}=\"${escapeHtml(prop.value.content)}\"`);\n      } else if (prop.type === NodeTypes.DIRECTIVE) {\n        // 处理 v-bind\n        if (prop.name === \"bind\" && prop.arg && prop.exp) {\n          // 处理 class、style 和其他属性\n        }\n      }\n    }\n\n    node.ssrCodegenNode = createTemplateLiteral(openTag);\n  };\n};\n```\n\n#### 属性绑定\n\n- **静态属性**：直接作为字符串输出\n- **v-bind:class**：使用 `ssrRenderClass` 辅助函数\n- **v-bind:style**：使用 `ssrRenderStyle` 辅助函数\n- **其他动态属性**：使用 `ssrRenderAttr` 或 `ssrRenderDynamicAttr`\n\n## v-if 转换\n\nv-if 被转换为 JavaScript if 语句．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVIf.ts\nexport function ssrProcessIf(node: IfNode, context: SSRTransformContext): void {\n  const [rootBranch] = node.branches;\n  const ifStatement = createIfStatement(\n    rootBranch.condition!,\n    processIfBranch(rootBranch, context),\n  );\n  context.pushStatement(ifStatement);\n\n  let currentIf = ifStatement;\n  for (let i = 1; i < node.branches.length; i++) {\n    const branch = node.branches[i];\n    const branchBlockStatement = processIfBranch(branch, context);\n    if (branch.condition) {\n      // else-if\n      currentIf = currentIf.alternate = createIfStatement(branch.condition, branchBlockStatement);\n    } else {\n      // else\n      currentIf.alternate = branchBlockStatement;\n    }\n  }\n\n  // 如果没有 else，输出空注释\n  if (!currentIf.alternate) {\n    currentIf.alternate = createBlockStatement([createCallExpression(`_push`, [\"`<!---->`\"])]);\n  }\n}\n```\n\n输入：\n```html\n<div v-if=\"show\">Visible</div>\n<div v-else>Hidden</div>\n```\n\n输出：\n```js\nif (show) {\n  _push(`<div>Visible</div>`)\n} else {\n  _push(`<div>Hidden</div>`)\n}\n```\n\n## v-for 转换\n\nv-for 使用 `ssrRenderList` 辅助函数进行转换．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVFor.ts\nexport function ssrProcessFor(node: ForNode, context: SSRTransformContext): void {\n  const renderLoop = createFunctionExpression(createForLoopParams(node.parseResult));\n  renderLoop.body = processChildrenAsStatement(node, context);\n\n  // Fragment 标记\n  context.pushStringPart(`<!--[-->`);\n  context.pushStatement(\n    createCallExpression(context.helper(SSR_RENDER_LIST), [node.source, renderLoop]),\n  );\n  context.pushStringPart(`<!--]-->`);\n}\n```\n\n输入：\n```html\n<div v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</div>\n```\n\n输出：\n```js\n_push(`<!--[-->`)\n_ssrRenderList(items, (item) => {\n  _push(`<div>${_ssrInterpolate(item.name)}</div>`)\n})\n_push(`<!--]-->`)\n```\n\n## SSR 辅助函数\n\nSSR 编译器使用以下运行时辅助函数，由 `@chibivue/server-renderer` 提供．\n\n```ts\n// packages/compiler-ssr/src/runtimeHelpers.ts\nexport const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`);\nexport const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`);\nexport const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`);\nexport const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`);\nexport const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`);\nexport const SSR_RENDER_DYNAMIC_ATTR: unique symbol = Symbol(`ssrRenderDynamicAttr`);\nexport const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`);\nexport const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(`ssrIncludeBooleanAttr`);\nexport const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`);\nexport const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`);\n```\n\n### 辅助函数的作用\n\n| 辅助函数 | 作用 |\n|---------|------|\n| `ssrInterpolate` | 转义文本插值 |\n| `ssrRenderAttrs` | 渲染对象格式的属性 |\n| `ssrRenderClass` | 渲染 class |\n| `ssrRenderStyle` | 渲染 style |\n| `ssrRenderList` | v-for 迭代 |\n| `ssrRenderComponent` | 创建组件 VNode |\n| `ssrRenderVNode` | 将 VNode 转换为 HTML 字符串 |\n\n## SFC 集成\n\ncompiler-sfc 支持 SSR 模式的编译．\n\n```ts\n// packages/compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  compiler,\n  compilerOptions,\n  id,\n  scoped,\n  ssr = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = (compiler || defaultCompiler).compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n  return { code, ast, source, preamble };\n}\n```\n\n指定 `ssr: true` 会自动使用 SSR 编译器．\n\n## 生成的代码示例\n\n输入模板：\n```html\n<div class=\"container\">\n  <h1>{{ title }}</h1>\n  <ul>\n    <li v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</li>\n  </ul>\n</div>\n```\n\n生成的代码：\n```js\nimport { ssrInterpolate as _ssrInterpolate, ssrRenderList as _ssrRenderList } from 'chibivue/server-renderer'\n\nfunction ssrRender(_ctx, _push, _parent, _attrs) {\n  _push(`<div class=\"container\"><h1>${_ssrInterpolate(_ctx.title)}</h1><ul><!--[-->`)\n  _ssrRenderList(_ctx.items, (item) => {\n    _push(`<li>${_ssrInterpolate(item.name)}</li>`)\n  })\n  _push(`<!--]--></ul></div>`)\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR 编译器的优点\">\n\n使用 SSR 编译器可以：\n- 没有 VNode 开销\n- 使用模板字面量高效生成字符串\n- 静态部分直接作为字符串输出\n\n这些提高了服务器端渲染的性能！\n\n</KawaikoNote>\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/030-builtins/010-keep-alive.md",
    "content": "# KeepAlive\n\n## 什么是 KeepAlive\n\n`<KeepAlive>` 是一个内置组件，它可以缓存并复用组件实例而不销毁它们．通常，当组件切换时，旧组件会被卸载，状态也会丢失．但是，通过使用 KeepAlive，您可以在切换组件时保留它们的状态．\n\n<KawaikoNote variant=\"question\" title=\"为什么需要 KeepAlive？\">\n\n例如，想象一个带有标签页切换的界面，其中一个标签页正在填写表单．\n如果您切换到另一个标签页再切换回来，输入的内容消失了会很令人沮丧．\nKeepAlive 就是为了满足这种\"保留状态\"的需求！\n\n</KawaikoNote>\n\n主要用例：\n\n1. **标签页切换**：在表单输入过程中切换标签页时保留输入内容\n2. **路由**：在页面导航期间保留滚动位置和输入状态\n3. **性能**：避免频繁切换的组件重新渲染\n\n## 基本用法\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentTab\" />\n  </KeepAlive>\n</template>\n```\n\n## 实现概述\n\n### Props 定义\n\n```ts\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n```\n\n- **include**：要缓存的组件名称（只有包含的才会被缓存）\n- **exclude**：要排除缓存的组件名称（包含的不会被缓存）\n- **max**：缓存的最大数量（使用 LRU 算法删除最旧的）\n\n### KeepAliveContext\n\nKeepAlive 组件有一个用于与渲染器交互的特殊上下文．\n\n```ts\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n```\n\n- **activate**：将缓存的组件恢复显示\n- **deactivate**：隐藏组件并缓存它\n\n## 核心逻辑实现\n\n### 缓存管理\n\n```ts\nconst cache: Map<any, VNode> = new Map();\nconst keys: Set<any> = new Set();\nlet current: VNode | null = null;\n\n// 用于存储非活动组件的隐藏容器\nconst storageContainer = instance.renderer.o.createElement(\"div\");\n```\n\nKeepAlive 使用 `cache` Map 来缓存组件的 VNode．`keys` Set 用于 LRU（最近最少使用）算法的顺序管理．\n\n### activate 函数\n\n从缓存中恢复组件并显示它．\n\n```ts\ninstance.activate = (vnode, container, anchor, _parentComponent) => {\n  const instance = vnode.component!;\n  // 从隐藏容器移动到实际容器\n  move(vnode, container, anchor);\n  // 应用任何 props 变化\n  patch(instance.vnode, vnode, container, anchor, parentComponent);\n  queuePostFlushCb(() => {\n    instance.isDeactivated = false;\n    // 调用 onActivated 钩子\n    if (instance.a) {\n      instance.a.forEach((hook: () => void) => hook());\n    }\n  });\n};\n```\n\n关键点：\n1. 将 DOM 从隐藏容器移动到目标容器\n2. 通过 patch 应用 props 变化\n3. 调用 `onActivated` 生命周期钩子\n\n### deactivate 函数\n\n隐藏并缓存组件．\n\n```ts\ninstance.deactivate = (vnode: VNode) => {\n  // 移动到隐藏容器（DOM 不会被删除）\n  move(vnode, storageContainer, null);\n  queuePostFlushCb(() => {\n    const instance = vnode.component!;\n    // 调用 onDeactivated 钩子\n    if (instance.da) {\n      instance.da.forEach((hook: () => void) => hook());\n    }\n    instance.isDeactivated = true;\n  });\n};\n```\n\n与正常卸载不同，DOM 元素不会被删除，只是移动到隐藏容器中．\n\n<KawaikoNote variant=\"funny\" title=\"隐藏容器技巧\">\n\n被隐藏的组件会被移动到屏幕外的\"藏身处\"．\n需要时，只需从\"藏身处\"取出即可，省去了重建的麻烦！\n\n</KawaikoNote>\n\n### render 函数\n\n这是 KeepAlive 的核心逻辑．\n\n```ts\nreturn (): VNode | undefined => {\n  if (!slots.default) {\n    return undefined;\n  }\n\n  const children = slots.default();\n  const rawVNode = children[0];\n\n  // 如果有多个子节点则不缓存\n  if (children.length > 1) {\n    current = null;\n    return children as unknown as VNode;\n  }\n\n  // 如果不是组件则直接返回\n  if (\n    !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n    !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n  ) {\n    current = null;\n    return rawVNode;\n  }\n\n  let vnode = rawVNode;\n  const comp = vnode.type as any;\n  const name = getComponentName(comp);\n  const { include, exclude, max } = props;\n\n  // include/exclude 过滤\n  if (\n    (include && (!name || !matches(include, name))) ||\n    (exclude && name && matches(exclude, name))\n  ) {\n    current = vnode;\n    return rawVNode;\n  }\n\n  // 确定缓存键\n  const key = vnode.key == null ? comp : vnode.key;\n  const cachedVNode = cache.get(key);\n\n  if (cachedVNode) {\n    // 缓存命中：恢复状态\n    vnode.el = cachedVNode.el;\n    vnode.component = cachedVNode.component;\n    vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n    // LRU：因为最近使用所以更新顺序\n    keys.delete(key);\n    keys.add(key);\n  } else {\n    // 新缓存条目\n    keys.add(key);\n    // 如果超过最大值则删除最旧的\n    if (max && keys.size > parseInt(max as string, 10)) {\n      pruneCacheEntry(keys.values().next().value);\n    }\n  }\n\n  // 设置标志让渲染器识别 KeepAlive\n  vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  current = vnode;\n  return vnode;\n};\n```\n\n### 通过 ShapeFlags 控制\n\nKeepAlive 使用 ShapeFlags 与渲染器协调．\n\n```ts\n// 此组件应由 KeepAlive 管理\nvnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n\n// 此组件是从缓存恢复的\nvnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n```\n\n渲染器检查这些标志，并调用 activate/deactivate 而不是正常的 mount/unmount．\n\n### include/exclude 匹配\n\n```ts\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n```\n\n模式支持以下格式：\n- 字符串（逗号分隔）：`\"ComponentA,ComponentB\"`\n- 正则表达式：`/^Tab/`\n- 数组：`[\"ComponentA\", /^Tab/]`\n\n### 缓存清理\n\n```ts\nfunction pruneCacheEntry(key: any): void {\n  const cached = cache.get(key) as VNode;\n  // 如果当前未显示则卸载\n  if (!current || !isSameVNodeType(cached, current)) {\n    unmount(cached);\n  } else if (current) {\n    // 如果当前正在显示则只重置标志\n    resetShapeFlag(current);\n  }\n  cache.delete(key);\n  keys.delete(key);\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n```\n\n## 生命周期钩子\n\n由 KeepAlive 管理的组件可以使用额外的生命周期钩子：\n\n- **onActivated**：当组件变为活动状态时\n- **onDeactivated**：当组件变为非活动状态时\n\n```ts\nimport { onActivated, onDeactivated } from 'vue'\n\nexport default {\n  setup() {\n    onActivated(() => {\n      console.log('activated!')\n    })\n    onDeactivated(() => {\n      console.log('deactivated!')\n    })\n  }\n}\n```\n\n## 使用示例\n\n### 基本用法\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### 使用 include/exclude\n\n```vue\n<template>\n  <!-- 只缓存 ComponentA 和 ComponentB -->\n  <KeepAlive include=\"ComponentA,ComponentB\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- 缓存除 ComponentC 以外的所有组件 -->\n  <KeepAlive exclude=\"ComponentC\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- 使用正则表达式匹配 -->\n  <KeepAlive :include=\"/^Tab/\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### 使用 max\n\n```vue\n<template>\n  <!-- 最多缓存 10 个组件（LRU） -->\n  <KeepAlive :max=\"10\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n## 与渲染器的集成\n\nKeepAlive 与渲染器紧密协调工作．\n\n### mountComponent 中的 KeepAlive 检测\n\n```ts\n// packages/runtime-core/src/renderer.ts\nconst mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {\n  const instance: ComponentInternalInstance = (\n    initialVNode.component = createComponentInstance(initialVNode, parentComponent)\n  );\n\n  // 对于 KeepAlive 组件，注入渲染器\n  if (isKeepAlive(initialVNode)) {\n    (instance as KeepAliveContext).renderer = {\n      p: patch,   // patch 函数\n      m: move,    // DOM 移动函数\n      um: unmount, // 卸载函数\n      o: options,  // 宿主选项（createElement 等）\n    };\n  }\n\n  // ... 正常的挂载处理\n};\n```\n\n### processComponent 中的 KEPT_ALIVE 检查\n\n```ts\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null = null,\n) => {\n  if (n1 == null) {\n    // 新挂载\n    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n      // 从缓存恢复：调用 activate\n      (parentComponent as KeepAliveContext).activate(\n        n2,\n        container,\n        anchor,\n        parentComponent as ComponentInternalInstance\n      );\n    } else {\n      // 正常挂载\n      mountComponent(n2, container, anchor, parentComponent);\n    }\n  } else {\n    updateComponent(n1, n2);\n  }\n};\n```\n\n### unmount 中的 SHOULD_KEEP_ALIVE 检查\n\n```ts\nconst unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {\n  const { type, shapeFlag, children } = vnode;\n\n  // KeepAlive 管理下的组件会被停用而不是删除\n  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n    (parentComponent as KeepAliveContext).deactivate(vnode);\n    return;\n  }\n\n  // 正常的卸载处理\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    unmountComponent(vnode.component!);\n  }\n  // ...\n};\n```\n\n## 处理流程\n\n```\n初次挂载：\nKeepAlive render\n  → 从插槽获取子节点\n  → 不在缓存中 → 添加到 keys\n  → 设置 COMPONENT_SHOULD_KEEP_ALIVE 标志\n  → 返回 vnode\n      ↓\nprocessComponent\n  → 没有 COMPONENT_KEPT_ALIVE → mountComponent\n  → isKeepAlive(vnode) → 注入渲染器\n  → 正常组件挂载\n\n从缓存恢复：\nKeepAlive render\n  → 从插槽获取子节点\n  → 缓存命中 → 复用 el/component\n  → 添加 COMPONENT_KEPT_ALIVE 标志\n  → 更新 keys 顺序（LRU）\n  → 返回 vnode\n      ↓\nprocessComponent\n  → 存在 COMPONENT_KEPT_ALIVE\n  → 调用 parentComponent.activate()\n      ↓\nactivate\n  → 从隐藏容器移动到实际容器\n  → 通过 patch 应用 props 变化\n  → instance.isDeactivated = false\n  → 调用 onActivated 钩子\n\n停用：\nunmount\n  → 存在 COMPONENT_SHOULD_KEEP_ALIVE\n  → 调用 parentComponent.deactivate()\n      ↓\ndeactivate\n  → 移动到隐藏容器（DOM 不会被删除）\n  → instance.isDeactivated = true\n  → 调用 onDeactivated 钩子\n  → 保留在缓存中\n```\n\n<KawaikoNote variant=\"warning\" title=\"注意内存使用！\">\n\n被 KeepAlive 缓存的组件会一直保留在内存中．\n缓存太多会占用内存，所以请使用 `max` 属性设置上限．\n它会通过 LRU（删除最近最少使用的项目）自动管理！\n\n</KawaikoNote>\n\n## 总结\n\nKeepAlive 的实现由以下元素组成：\n\n1. **缓存系统**：使用 Map 和 Set 的 LRU 缓存\n2. **隐藏容器**：保存非活动的 DOM（`createElement(\"div\")`）\n3. **activate/deactivate**：DOM 移动和生命周期管理\n4. **ShapeFlags**：与渲染器协调\n   - `COMPONENT_SHOULD_KEEP_ALIVE`：卸载时调用 deactivate\n   - `COMPONENT_KEPT_ALIVE`：挂载时调用 activate\n5. **渲染器注入**：KeepAlive 持有 patch/move/unmount 函数的引用\n6. **include/exclude/max**：灵活的缓存控制\n\nKeepAlive 是一个强大的功能，可以在保留组件状态的同时提高性能，但需要权衡内存使用，因此设置适当的 `max` 值很重要．\n\n<KawaikoNote variant=\"surprise\" title=\"KeepAlive 完成！\">\n\n\"隐藏而不是删除\"组件是一个简单的想法，\n但与渲染器协调和 LRU 缓存的实现相当深入！\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/020_keep_alive)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/030-builtins/020-suspense.md",
    "content": "---\nwip: true\n---\n\n# Coming Soon\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/030-builtins/030-transition.md",
    "content": "# Transition\n\n## 什么是 Transition？\n\n`<Transition>` 是一个内置组件，用于在显示或隐藏元素和组件时应用动画．它与 CSS 过渡/动画配合使用，实现平滑的 UI 过渡效果．\n\n<KawaikoNote variant=\"question\" title=\"为什么需要 Transition？\">\n\n当你使用 `v-if` 切换元素的显示/隐藏时，元素会瞬间消失或出现．\n使用 Transition，你可以轻松添加淡入/淡出或滑动等动画效果！\n\n</KawaikoNote>\n\n主要用例：\n\n1. **与 v-if / v-show 组合**：条件渲染时的动画\n2. **动态组件**：使用 `<component :is>` 时的切换动画\n3. **路由过渡**：页面之间的过渡效果\n\n## 基本用法\n\n```vue\n<template>\n  <button @click=\"show = !show\">Toggle</button>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n## 实现概述\n\n### Props 定义\n\n```ts\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  appearFromClass?: string;\n  appearActiveClass?: string;\n  appearToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  appear?: boolean;\n  // 生命周期钩子\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onEnterCancelled?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n  onLeaveCancelled?: (el: Element) => void;\n  // appear 钩子\n  onBeforeAppear?: (el: Element) => void;\n  onAppear?: (el: Element, done: () => void) => void;\n  onAfterAppear?: (el: Element) => void;\n  onAppearCancelled?: (el: Element) => void;\n}\n```\n\n### TransitionHooks 接口\n\n```ts\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n```\n\n渲染器通过此接口与 Transition 进行协调．\n\n## CSS 类的生命周期\n\nTransition 会自动添加和移除以下 CSS 类：\n\n### Enter（显示元素）\n\n1. **v-enter-from**：开始状态．在元素插入之前添加，1 帧后移除\n2. **v-enter-active**：激活状态．在整个过渡期间应用\n3. **v-enter-to**：结束状态．开始后 1 帧添加，过渡结束时移除\n\n### Leave（隐藏元素）\n\n1. **v-leave-from**：开始状态．在 leave 过渡开始时添加，1 帧后移除\n2. **v-leave-active**：激活状态．在整个过渡期间应用\n3. **v-leave-to**：结束状态．开始后 1 帧添加，过渡结束时移除\n\n```\nEnter:\n┌──────────────────────────────────────────┐\n│ v-enter-from → (1 frame) → v-enter-to   │\n│ ├─────── v-enter-active ──────────────┤ │\n└──────────────────────────────────────────┘\n\nLeave:\n┌──────────────────────────────────────────┐\n│ v-leave-from → (1 frame) → v-leave-to   │\n│ ├─────── v-leave-active ──────────────┤ │\n└──────────────────────────────────────────┘\n```\n\n## 核心逻辑实现\n\n### resolveTransitionProps\n\n解析 props 并生成 TransitionHooks．\n\n```ts\nexport function resolveTransitionProps(\n  rawProps: TransitionProps\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    // ... 其他类\n    mode = \"default\",\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  // 生成钩子函数\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      callHook(onBeforeEnter, [el]);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter: makeEnterHook(false),\n    leave(el, done) {\n      // leave 逻辑\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n```\n\n### CSS 类管理\n\n```ts\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function addTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n```\n\n`_vtc`（Vue Transition Classes）属性用于跟踪当前应用的过渡类．\n\n### nextFrame\n\n为了使 CSS 过渡正常工作，我们需要等待 2 帧后再更改类．\n\n```ts\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n```\n\n第一帧让浏览器识别初始状态，第二帧应用更改，确保过渡可靠触发．\n\n<KawaikoNote variant=\"funny\" title=\"为什么要等待 2 帧？\">\n\n\"为什么要调用两次 `requestAnimationFrame`？\"你可能会疑惑．\n第一次调用告诉浏览器\"这是初始状态\"，\n第二次调用告诉它\"这是结束状态\"，\n这样浏览器才能识别过渡！\n\n</KawaikoNote>\n\n### Enter 钩子\n\n```ts\nconst makeEnterHook = (isAppear: boolean) => {\n  return (el: Element, done: () => void) => {\n    const hook = isAppear ? onAppear : onEnter;\n    const resolve = () => finishEnter(el, isAppear, done);\n\n    callHook(hook, [el, resolve]);\n\n    nextFrame(() => {\n      removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass);\n      addTransitionClass(el, isAppear ? appearToClass : enterToClass);\n      if (!hasExplicitCallback(hook)) {\n        whenTransitionEnds(el, type, enterDuration, resolve);\n      }\n    });\n  };\n};\n```\n\n1. 调用用户定义的钩子\n2. 2 帧后，移除 from 类并添加 to 类\n3. 检测过渡结束并完成处理\n\n### Leave 钩子\n\n```ts\nleave(el, done) {\n  const resolve = () => finishLeave(el, done);\n  addTransitionClass(el, leaveFromClass);\n  // 强制重排\n  forceReflow();\n  addTransitionClass(el, leaveActiveClass);\n\n  nextFrame(() => {\n    removeTransitionClass(el, leaveFromClass);\n    addTransitionClass(el, leaveToClass);\n    if (!hasExplicitCallback(onLeave)) {\n      whenTransitionEnds(el, type, leaveDuration, resolve);\n    }\n  });\n  callHook(onLeave, [el, resolve]);\n}\n```\n\n## 检测过渡结束\n\n### getTransitionInfo\n\n从 CSS 获取 transition/animation 信息．\n\n```ts\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"]\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  // 确定使用 transition 还是 animation\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    // animation 的情况\n  } else {\n    // 自动检测\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0\n      ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION)\n      : null;\n  }\n\n  return { type, timeout, propCount, hasTransform };\n}\n```\n\n### whenTransitionEnds\n\n在过渡结束时执行回调．\n\n```ts\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  // 如果提供了显式超时，则使用它\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout);\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\"; // \"transitionend\" 或 \"animationend\"\n  let ended = 0;\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  // 超时回退\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n```\n\n要点：\n- 监听 `transitionend` / `animationend` 事件\n- 等待与属性数量相同的事件\n- 超时回退（以防事件未触发的保险）\n- `_endId` 取消旧的过渡\n\n### forceReflow\n\n强制重排以确保 CSS 过渡可靠触发．\n\n```ts\nexport function forceReflow(): number {\n  return document.body.offsetHeight;\n}\n```\n\n读取 `offsetHeight` 强制浏览器重新计算样式．\n\n<KawaikoNote variant=\"warning\" title=\"为什么要强制重排？\">\n\n即使连续添加 CSS 类，浏览器也可能为了优化而批量进行样式重新计算．\n读取 `offsetHeight` 可以强制它\"立即计算！\"\n\n</KawaikoNote>\n\n## Transition 组件主体\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // 在 VNode 上设置 transition 钩子\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\nTransition 本身不渲染任何 DOM 元素；它只是在子 VNode 上附加一个 `transition` 属性．渲染器会看到这个属性并调用钩子．\n\n## 使用示例\n\n### 基本淡入淡出\n\n```vue\n<template>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n### 滑动动画\n\n```vue\n<template>\n  <Transition name=\"slide\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.slide-enter-active,\n.slide-leave-active {\n  transition: all 0.3s ease;\n}\n.slide-enter-from {\n  transform: translateX(-100%);\n  opacity: 0;\n}\n.slide-leave-to {\n  transform: translateX(100%);\n  opacity: 0;\n}\n</style>\n```\n\n### JavaScript 钩子\n\n```vue\n<template>\n  <Transition\n    @before-enter=\"onBeforeEnter\"\n    @enter=\"onEnter\"\n    @after-enter=\"onAfterEnter\"\n    @leave=\"onLeave\"\n    :css=\"false\"\n  >\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<script setup>\nfunction onBeforeEnter(el) {\n  el.style.opacity = 0;\n}\n\nfunction onEnter(el, done) {\n  // 使用 GSAP 等动画库\n  gsap.to(el, {\n    opacity: 1,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n\nfunction onLeave(el, done) {\n  gsap.to(el, {\n    opacity: 0,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n</script>\n```\n\n### 显式 duration\n\n```vue\n<template>\n  <!-- enter: 300ms, leave: 500ms -->\n  <Transition name=\"fade\" :duration=\"{ enter: 300, leave: 500 }\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n```\n\n## 与 VNode 的集成\n\n### VNode.transition 属性\n\nVNode 有一个 `transition` 属性，用于存储 TransitionHooks．\n\n```ts\n// packages/runtime-core/src/vnode.ts\nexport interface VNode<HostNode = any> {\n  // ... 其他属性\n\n  // transition\n  transition: any | null;\n}\n```\n\n### 在 Transition 组件中设置\n\nTransition 组件在子 VNode 上设置 `transition` 属性．\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // 在 VNode 上设置 transition 钩子\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\n### 渲染器中的处理\n\n渲染器检测 VNode 的 `transition` 属性，并在适当的时机调用钩子：\n\n1. **插入元素时**：`beforeEnter` → DOM 插入 → `enter`\n2. **移除元素时**：`leave` → DOM 移除\n\n```ts\n// 概念性处理流程\nconst mountElement = (vnode, container, anchor) => {\n  const el = createElement(vnode.type);\n\n  // 如果有 transition，调用 beforeEnter\n  if (vnode.transition) {\n    vnode.transition.beforeEnter(el);\n  }\n\n  // 插入 DOM\n  insert(el, container, anchor);\n\n  // 如果有 transition，调用 enter\n  if (vnode.transition) {\n    vnode.transition.enter(el);\n  }\n};\n\nconst unmountElement = (vnode) => {\n  const el = vnode.el;\n\n  // 如果有 transition，调用 leave\n  if (vnode.transition) {\n    vnode.transition.leave(el, () => {\n      // leave 完成后从 DOM 移除\n      remove(el);\n    });\n  } else {\n    remove(el);\n  }\n};\n```\n\n## 处理流程\n\n```\nTransition 组件 render\n  ↓\n使用 resolveTransitionProps 生成 TransitionHooks\n  ↓\nchild.transition = innerProps\n  ↓\n渲染器 mountElement\n  ├── beforeEnter(el)\n  │   └── 添加 enterFromClass/enterActiveClass\n  ├── insert(el, container)\n  └── enter(el, done)\n      └── 在 nextFrame 中\n          ├── 移除 enterFromClass\n          ├── 添加 enterToClass\n          └── 使用 whenTransitionEnds 等待完成\n              └── done() 调用 finishEnter\n\n渲染器 unmountElement\n  └── transition.leave(el, remove)\n      ├── 添加 leaveFromClass\n      ├── forceReflow()\n      ├── 添加 leaveActiveClass\n      └── 在 nextFrame 中\n          ├── 移除 leaveFromClass\n          ├── 添加 leaveToClass\n          └── 使用 whenTransitionEnds 等待完成\n              └── remove() 从 DOM 移除\n```\n\n## 总结\n\nTransition 的实现由以下元素组成：\n\n1. **CSS 类管理**：在 enter/leave 的每个阶段添加/移除类\n2. **nextFrame**：等待 2 帧以保证过渡触发\n3. **forceReflow**：强制重排以重新计算样式\n4. **whenTransitionEnds**：监听 transitionend/animationend 事件\n5. **JavaScript 钩子**：支持不使用 CSS 的动画\n6. **VNode.transition**：供渲染器调用钩子的属性\n\nTransition 与 CSS 过渡/动画密切配合，其实现基于对浏览器渲染管道的深入理解．\n\n<KawaikoNote variant=\"surprise\" title=\"Transition 完成！\">\n\n不仅仅是 CSS 类操作，还有帧时序控制和重排管理——\n这个实现需要深入理解浏览器内部机制．\n出乎意料地深奥，不是吗！\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/030_transition)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/040-optimizations/010-static-hoisting.md",
    "content": "# Static Hoisting（静态提升）\n\n## 什么是 Static Hoisting\n\nStatic Hoisting 是模板编译时的优化技术之一．它检测模板中的静态节点（没有响应式依赖的节点），并将它们\"提升\"（hoist）到渲染函数外部，从而提高重新渲染时的性能．\n\n<KawaikoNote variant=\"question\" title=\"为什么叫提升（hoist）？\">\n\n这与 JavaScript 的\"变量提升（hoisting）\"是相同的概念．\n通过将渲染函数内的静态代码\"提升\"到函数外部，\n每次调用函数时就不需要重新生成了！\n\n</KawaikoNote>\n\n### 优化效果\n\n1. **跳过 VNode 生成**：静态节点只生成一次，之后重复使用\n2. **减少内存使用**：重复使用相同的 VNode 对象\n3. **跳过 patch 处理**：静态节点可以从比较对象中排除\n\n## 优化前后对比\n\n### 模板\n\n```vue\n<template>\n  <div>\n    <h1>Hello World</h1>\n    <p>{{ message }}</p>\n  </div>\n</template>\n```\n\n### 无优化的编译结果\n\n```js\nfunction render() {\n  return h('div', null, [\n    h('h1', null, 'Hello World'),  // 每次都生成\n    h('p', null, message.value)\n  ])\n}\n```\n\n### 应用 Static Hoisting 后\n\n```js\nconst _hoisted_1 = h('h1', null, 'Hello World')  // 在外部只生成一次\n\nfunction render() {\n  return h('div', null, [\n    _hoisted_1,  // 重复使用引用\n    h('p', null, message.value)\n  ])\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"戏剧性的前后对比！\">\n\n从每次都生成 VNode 变成只重复使用一次生成的 VNode．\n像页眉页脚这样不变的部分越多，效果就越显著！\n\n</KawaikoNote>\n\n## 实现概要\n\n### ConstantTypes\n\n表示节点静态性的枚举类型．\n\n```ts\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,    // 动态（不可提升）\n  CAN_SKIP_PATCH = 1,  // 可跳过 patch 处理\n  CAN_HOIST = 2,       // 可提升\n  CAN_STRINGIFY = 3,   // 可字符串化（可进一步优化）\n}\n```\n\n### hoistStatic 函数\n\n在变换阶段之后调用，检测并提升静态节点．\n\n```ts\nexport function hoistStatic(root: RootNode, context: TransformContext): void {\n  walk(root, context, new Map());\n}\n```\n\n### walk 函数\n\n递归遍历 AST，检测可提升的节点．\n\n```ts\nfunction walk(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): void {\n  const { children } = node as RootNode | ElementNode;\n  if (!children) return;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (\n      child.type === NodeTypes.ELEMENT &&\n      child.tagType === 0 // 仅针对普通元素\n    ) {\n      const constantType = getConstantType(child, context, resultCache);\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          // 可提升\n          const codegenNode = child.codegenNode as VNodeCall | undefined;\n          if (codegenNode && codegenNode.type === NodeTypes.VNODE_CALL) {\n            codegenNode.isStatic = true;\n            context.hoists.push(codegenNode);\n            // 将 codegenNode 替换为提升引用\n            child.codegenNode = context.hoist(codegenNode) as VNodeCall;\n          }\n        }\n      } else {\n        // 如果是动态的，递归检查子节点\n        walk(child, context, resultCache);\n      }\n    }\n  }\n}\n```\n\n要点：\n1. 仅针对普通元素（非组件）\n2. 静态节点添加到 `context.hoists`\n3. 将原始 `codegenNode` 替换为 `_hoisted_N` 的引用\n4. 动态节点递归检查子节点\n\n### getConstantType 函数\n\n判断节点是否为静态．\n\n```ts\nexport function getConstantType(\n  node: TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): ConstantTypes {\n  // 检查缓存\n  const cached = resultCache.get(node);\n  if (cached !== undefined) {\n    return cached;\n  }\n\n  if (node.type === NodeTypes.ELEMENT) {\n    // 组件不可提升\n    if (node.tagType !== 0) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    const element = node as PlainElementNode;\n    const codegenNode = element.codegenNode;\n\n    if (!codegenNode || codegenNode.type !== NodeTypes.VNODE_CALL) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    // 检查是否有动态 props\n    if (codegenNode.props) {\n      const propsType = codegenNode.props.type;\n      if (propsType !== NodeTypes.JS_OBJECT_EXPRESSION) {\n        resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n\n      const properties = codegenNode.props.properties;\n      for (let i = 0; i < properties.length; i++) {\n        const { key, value } = properties[i];\n        // 如果键和值都不是静态的则不可\n        if (key.type !== NodeTypes.SIMPLE_EXPRESSION || !key.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n        if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // 递归检查子元素\n    if (element.children) {\n      for (let i = 0; i < element.children.length; i++) {\n        const child = element.children[i];\n        const childType = getConstantType(child, context, resultCache);\n        if (childType === ConstantTypes.NOT_CONSTANT) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // 如果有指令则不可\n    if (element.props && element.props.length > 0) {\n      for (const prop of element.props) {\n        if (prop.type === NodeTypes.DIRECTIVE) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    resultCache.set(node, ConstantTypes.CAN_HOIST);\n    return ConstantTypes.CAN_HOIST;\n  }\n\n  // 文本节点可提升\n  if (node.type === NodeTypes.TEXT) {\n    resultCache.set(node, ConstantTypes.CAN_STRINGIFY);\n    return ConstantTypes.CAN_STRINGIFY;\n  }\n\n  // 插值（{{ }}）是动态的\n  if (node.type === NodeTypes.INTERPOLATION) {\n    resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n    return ConstantTypes.NOT_CONSTANT;\n  }\n\n  resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n  return ConstantTypes.NOT_CONSTANT;\n}\n```\n\n判断逻辑：\n1. **组件**：始终是动态的（props 和 slots 可能会变化）\n2. **动态 props**：如果有绑定（`:class`，`:style` 等）则是动态的\n3. **指令**：如果有 `v-if`，`v-for` 等则是动态的\n4. **插值表达式**：`{{ message }}` 是动态的\n5. **子元素**：只要有一个子元素是动态的，父元素也是动态的\n6. **静态文本/属性**：可提升\n\n### 代码生成\n\n```ts\nfunction genHoists(\n  hoists: (TemplateChildNode | ExpressionNode)[],\n  context: CodegenContext\n) {\n  const { push, newline } = context;\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context);\n      newline();\n    }\n  }\n}\n```\n\n将累积在 `hoists` 数组中的节点作为常量生成在渲染函数之前．\n\n### TransformContext 的 hoist 方法\n\n```ts\nhoist(exp) {\n  context.hoists.push(exp);\n  const identifier = createSimpleExpression(\n    `_hoisted_${context.hoists.length}`,\n    false,\n  );\n  return identifier;\n}\n```\n\n将原始节点添加到 `hoists` 数组，并返回 `_hoisted_N` 标识符．这在渲染函数内被引用．\n\n## 可提升的示例\n\n```vue\n<template>\n  <!-- 可提升 -->\n  <div class=\"static\">Static content</div>\n  <img src=\"/logo.png\" alt=\"Logo\">\n  <p>Fixed text</p>\n\n  <!-- 不可提升 -->\n  <div :class=\"dynamicClass\">Dynamic</div>\n  <p>{{ message }}</p>\n  <div v-if=\"show\">Conditional</div>\n  <MyComponent />\n</template>\n```\n\n## 在 transform 阶段的调用\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions): void {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n\n  // 如果启用了 hoistStatic 选项则执行\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n\n  createRootCodegen(root, context);\n  root.components = [...context.components];\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.hoists = context.hoists;\n}\n```\n\n## 选项\n\n```ts\nexport interface TransformOptions {\n  hoistStatic?: boolean;  // 启用静态提升\n  // ...\n}\n```\n\n## 生成代码示例\n\n输入模板：\n```vue\n<template>\n  <div>\n    <header>\n      <h1>My App</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ content }}</p>\n    </main>\n  </div>\n</template>\n```\n\n生成的代码：\n```js\nimport { createVNode as _createVNode, toDisplayString as _toDisplayString } from 'vue'\n\n// 静态节点提升到外部\nconst _hoisted_1 = _createVNode(\"header\", null, [\n  _createVNode(\"h1\", null, \"My App\"),\n  _createVNode(\"nav\", null, [\n    _createVNode(\"a\", { href: \"/home\" }, \"Home\"),\n    _createVNode(\"a\", { href: \"/about\" }, \"About\")\n  ])\n])\n\nfunction render(_ctx) {\n  return _createVNode(\"div\", null, [\n    _hoisted_1,  // 重复使用引用\n    _createVNode(\"main\", null, [\n      _createVNode(\"p\", null, _toDisplayString(_ctx.content))  // 动态部分\n    ])\n  ])\n}\n```\n\n## 总结\n\nStatic Hoisting 的实现由以下元素组成：\n\n1. **ConstantTypes**：表示节点静态级别的枚举类型\n2. **getConstantType**：判断节点是否为静态\n3. **walk**：遍历 AST 检测可提升的节点\n4. **hoist**：将节点添加到提升数组并返回引用\n5. **genHoists**：为提升的节点生成代码\n\n这种优化在具有大量静态内容的大型模板中显著提高重新渲染性能．特别是对于页眉，页脚，侧边栏等不变的 UI 部分效果显著．\n\n<KawaikoNote variant=\"surprise\" title=\"Static Hoisting 完成！\">\n\n编译器自动判断\"这部分不会变化\"并进行优化．\n这是基于模板的框架独有的优势！\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/040_static_hoisting)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/040-optimizations/020-patch-flags.md",
    "content": "# Patch Flags\n\n## 什么是 Patch Flags？\n\nPatch Flags 是编译器生成的优化提示．通过为 VNode 添加标志，运行时的差分检测（diffing）算法可以跳过不必要的检查，从而提高性能．\n\n<KawaikoNote variant=\"question\" title=\"为什么由编译器来优化？\">\n\n编写模板的人类知道「这里是动态的」「这里是静态的」，\n但传统的 Virtual DOM 并不知道这些．通过让编译器将这些信息传递给运行时，\n就可以省去不必要的比较！\n\n</KawaikoNote>\n\n### 优化的机制\n\n在普通的 Virtual DOM 差分检测中，需要比较所有的属性和子元素．然而，编译器在模板解析阶段就知道「哪些部分是动态的」．通过将这些信息作为 Patch Flags 嵌入到 VNode 中，运行时只需检查可能发生变化的部分．\n\n## PatchFlags 的定义\n\n```ts\nexport const enum PatchFlags {\n  /**\n   * 具有动态 textContent 的元素\n   */\n  TEXT = 1,\n\n  /**\n   * 具有动态 class 绑定的元素\n   */\n  CLASS = 1 << 1,  // 2\n\n  /**\n   * 具有动态 style 的元素\n   */\n  STYLE = 1 << 2,  // 4\n\n  /**\n   * 具有 class/style 以外的动态 props 的元素\n   */\n  PROPS = 1 << 3,  // 8\n\n  /**\n   * 具有动态键的 props 的元素\n   */\n  FULL_PROPS = 1 << 4,  // 16\n\n  /**\n   * hydration 时需要处理 props\n   */\n  NEED_HYDRATION = 1 << 5,  // 32\n\n  /**\n   * 子元素顺序不变的 Fragment\n   */\n  STABLE_FRAGMENT = 1 << 6,  // 64\n\n  /**\n   * 具有 keyed 子元素的 Fragment\n   */\n  KEYED_FRAGMENT = 1 << 7,  // 128\n\n  /**\n   * 具有非 keyed 子元素的 Fragment\n   */\n  UNKEYED_FRAGMENT = 1 << 8,  // 256\n\n  /**\n   * 需要 props 以外的 patch（ref、指令等）\n   */\n  NEED_PATCH = 1 << 9,  // 512\n\n  /**\n   * 具有动态插槽的组件\n   */\n  DYNAMIC_SLOTS = 1 << 10,  // 1024\n\n  /**\n   * 开发用：根部有注释的 Fragment\n   */\n  DEV_ROOT_FRAGMENT = 1 << 11,  // 2048\n\n  // 特殊标志（负整数）\n\n  /**\n   * 缓存的静态 VNode\n   */\n  CACHED = -1,\n\n  /**\n   * 退出优化模式的提示\n   */\n  BAIL = -2,\n}\n```\n\n## 通过位运算进行组合\n\nPatch Flags 被设计为位标志，可以组合多个标志．\n\n```ts\n// 组合标志\nconst flag = PatchFlags.TEXT | PatchFlags.CLASS;  // 3 (0b11)\n\n// 检查标志\nif (flag & PatchFlags.TEXT) {\n  // TEXT 标志已设置\n}\n\nif (flag & PatchFlags.CLASS) {\n  // CLASS 标志已设置\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"位运算的魔法\">\n\n`1 << 1` 是 `2`，`1 << 2` 是 `4`...只需移动位就能创建独立的标志．\n用 `|`（OR）组合，用 `&`（AND）检查．简单但超高效！\n\n</KawaikoNote>\n\n## 从模板生成的示例\n\n### 动态文本\n\n```vue\n<template>\n  <p>{{ message }}</p>\n</template>\n```\n\n生成的代码：\n```js\n// patchFlag = 1 (TEXT)\ncreateVNode(\"p\", null, toDisplayString(message), 1 /* TEXT */)\n```\n\n### 动态类\n\n```vue\n<template>\n  <div :class=\"dynamicClass\">Content</div>\n</template>\n```\n\n生成的代码：\n```js\n// patchFlag = 2 (CLASS)\ncreateVNode(\"div\", { class: dynamicClass }, \"Content\", 2 /* CLASS */)\n```\n\n### 多个动态属性\n\n```vue\n<template>\n  <div :class=\"cls\" :style=\"styles\">{{ text }}</div>\n</template>\n```\n\n生成的代码：\n```js\n// patchFlag = 7 (TEXT | CLASS | STYLE)\ncreateVNode(\"div\",\n  { class: cls, style: styles },\n  toDisplayString(text),\n  7 /* TEXT, CLASS, STYLE */\n)\n```\n\n### 动态 props\n\n```vue\n<template>\n  <input :value=\"inputValue\" :disabled=\"isDisabled\">\n</template>\n```\n\n生成的代码：\n```js\n// patchFlag = 8 (PROPS)\n// dynamicProps 明确指定可能变化的 props\ncreateVNode(\"input\",\n  { value: inputValue, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]\n)\n```\n\n## 在运行时的应用\n\n### patchElement 中的优化\n\n```ts\nfunction patchElement(n1: VNode, n2: VNode) {\n  const el = n2.el = n1.el;\n  const { patchFlag, dynamicProps } = n2;\n\n  if (patchFlag > 0) {\n    // 优化路径：根据标志只更新必要的部分\n\n    if (patchFlag & PatchFlags.CLASS) {\n      // 只更新 class\n      if (n1.props?.class !== n2.props?.class) {\n        hostSetClass(el, n2.props?.class);\n      }\n    }\n\n    if (patchFlag & PatchFlags.STYLE) {\n      // 只更新 style\n      hostPatchStyle(el, n1.props?.style, n2.props?.style);\n    }\n\n    if (patchFlag & PatchFlags.PROPS) {\n      // 只更新指定的 props\n      for (const key of dynamicProps!) {\n        const prev = n1.props?.[key];\n        const next = n2.props?.[key];\n        if (prev !== next) {\n          hostPatchProp(el, key, prev, next);\n        }\n      }\n    }\n\n    if (patchFlag & PatchFlags.TEXT) {\n      // 只更新文本内容\n      if (n1.children !== n2.children) {\n        hostSetElementText(el, n2.children as string);\n      }\n    }\n  } else if (patchFlag === PatchFlags.FULL_PROPS) {\n    // 检查所有 props\n    patchProps(el, n1.props, n2.props);\n  } else {\n    // 无标志：完整 diff\n    patchProps(el, n1.props, n2.props);\n    patchChildren(n1, n2, el);\n  }\n}\n```\n\n### Fragment 的优化\n\n```ts\nfunction patchFragment(n1: VNode, n2: VNode) {\n  const { patchFlag } = n2;\n\n  if (patchFlag & PatchFlags.STABLE_FRAGMENT) {\n    // 子元素顺序不变：简单更新\n    patchBlockChildren(n1.children, n2.children);\n  } else if (patchFlag & PatchFlags.KEYED_FRAGMENT) {\n    // keyed 子元素：基于 key 的 diff\n    patchKeyedChildren(n1.children, n2.children);\n  } else {\n    // unkeyed：完整 diff\n    patchUnkeyedChildren(n1.children, n2.children);\n  }\n}\n```\n\n## 特殊标志\n\n### CACHED (-1)\n\n表示静态 VNode 已被缓存．\n\n```js\nconst _hoisted_1 = createVNode(\"div\", null, \"Static\", -1 /* CACHED */);\n```\n\n缓存的 VNode 可以跳过差分检测．\n\n### BAIL (-2)\n\n退出优化模式的提示．当用户使用手写的 render 函数等编译器优化无法应用的情况下使用．\n\n## dynamicProps\n\n与 `patchFlag` 一起使用的 `dynamicProps` 数组明确指定哪些 props 是动态的．\n\n```ts\n// 动态 props 是 value 和 disabled\ncreateVNode(\"input\",\n  { type: \"text\", value: val, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]  // dynamicProps\n)\n```\n\n这样，由于 `type` 是静态的，可以跳过比较，只检查 `value` 和 `disabled`．\n\n## 与 Block Tree 的协作\n\nPatch Flags 与 Block Tree 优化协同工作．Block 拥有 `dynamicChildren` 数组，只追踪动态子节点．\n\n```ts\nconst block = openBlock();\nconst vnode = createBlock(\"div\", null, [\n  createVNode(\"p\", null, \"static\"),  // 不包含在 dynamicChildren 中\n  createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // 包含\n]);\n// block.dynamicChildren = [只有动态的 p]\n```\n\n更新 Block 时只需遍历 `dynamicChildren`，因此可以跳过静态子节点的比较．\n\n## 优化的效果\n\n### 优化前（无标志）\n```\n比较所有 props: O(n)\n比较所有子元素: O(m)\n总计: O(n + m)\n```\n\n### 优化后（有标志）\n```\n只比较动态 props: O(k) 其中 k << n\n只比较动态子元素: O(l) 其中 l << m\n总计: O(k + l)\n```\n\n当模板的大部分是静态的时候，这种优化会产生显著的效果．\n\n## 总结\n\nPatch Flags 的实现由以下要素组成：\n\n1. **位标志**：高效地表示多个动态元素\n2. **编译器集成**：在模板解析时自动生成\n3. **运行时优化**：根据标志跳过不必要的比较\n4. **dynamicProps**：明确追踪动态 props\n5. **Block Tree 协作**：只高效更新动态子节点\n\nPatch Flags 是大幅提升 Vue 3 Virtual DOM 性能的重要优化技术．通过编译器和运行时的协作，最大限度地发挥了基于模板的框架的优势．\n\n<KawaikoNote variant=\"surprise\" title=\"Patch Flags 完成！\">\n\n这项技术源于「既然能解析模板，那也能提供优化提示」的想法．\n请亲身体验 JSX 所没有的模板编译器的优势！\n\n</KawaikoNote>\n\n到此为止的源代码：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/050_patch_flags)\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/040-optimizations/030-tree-flattening.md",
    "content": "# Tree Flattening（Block Tree）\n\n## 什么是 Tree Flattening？\n\nTree Flattening（Block Tree）是 Vue 3 引入的高级优化技术．它\"扁平化\"并收集模板内的动态节点，允许在更新时直接更新动态节点，而不是遍历整个树．\n\n<KawaikoNote variant=\"question\" title=\"为什么叫'扁平化'？\">\n\n传统的 Virtual DOM 在更新时需要递归遍历整个树．\nTree Flattening 将动态节点\"扁平化\"到数组中，\n允许直接访问而忽略嵌套结构．\n\n</KawaikoNote>\n\n## 传统 diff 算法的问题\n\n### 模板示例\n\n```vue\n<template>\n  <div>\n    <header>\n      <h1>Static Title</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ dynamicText }}</p>  <!-- 唯一的动态部分 -->\n    </main>\n    <footer>\n      <p>Copyright 2024</p>\n    </footer>\n  </div>\n</template>\n```\n\n### 传统方法\n\n```\n遍历整个树：\ndiv\n├── header (静态)\n│   ├── h1 (静态)\n│   └── nav (静态)\n│       ├── a (静态)\n│       └── a (静态)\n├── main\n│   └── p (动态) ← 实际上只有这里需要更新\n└── footer (静态)\n    └── p (静态)\n\n→ 遍历 9 个节点来更新 1 个\n```\n\n### Tree Flattening 方法\n\n```\n只收集动态节点：\ndynamicChildren = [p]\n\n→ 直接更新 1 个节点\n```\n\n<KawaikoNote variant=\"funny\" title=\"戏剧性的效率提升！\">\n\n如果 1000 个节点中只有 10 个是动态的：\n传统方法需要 1000 次比较，\n但 Tree Flattening 只需要 10 次比较．\n\n</KawaikoNote>\n\n## Block 概念\n\n### 什么是 Block？\n\nBlock 是\"具有稳定结构的 VNode 子树\"．在 Block 内，保证以下几点：\n\n1. 子节点数量不变\n2. 子节点顺序不变\n3. 没有结构性指令（`v-if`，`v-for`）\n\n### 创建 Block 的元素\n\n以下元素会创建新的 Block：\n\n- 根元素\n- `v-if` 的每个分支\n- `v-for` 的每个项目\n- 组件\n\n```vue\n<template>\n  <!-- Block 1: 根 -->\n  <div>\n    <p>{{ text1 }}</p>\n\n    <!-- Block 2: v-if -->\n    <div v-if=\"show\">\n      <p>{{ text2 }}</p>\n    </div>\n\n    <!-- Block 3, 4, ...: 每个 v-for 项目 -->\n    <div v-for=\"item in items\" :key=\"item.id\">\n      <p>{{ item.text }}</p>\n    </div>\n  </div>\n</template>\n```\n\n## VNode 扩展\n\n### dynamicChildren\n\n向 VNode 添加 `dynamicChildren` 属性来收集动态子节点．\n\n```ts\nexport interface VNode {\n  // ... 现有属性\n\n  /**\n   * Block 内动态子节点的列表\n   * 更新时只需要遍历这些\n   */\n  dynamicChildren: VNode[] | null;\n\n  /**\n   * patch 处理的优化提示\n   */\n  patchFlag: number;\n\n  /**\n   * 动态属性名称列表\n   */\n  dynamicProps: string[] | null;\n}\n```\n\n## openBlock 和 createBlock\n\n### Block 追踪\n\nBlock 的创建通过 `openBlock` 和 `createBlock` 配对完成．\n\n```ts\n// 当前追踪的 Block\nlet currentBlock: VNode[] | null = null;\n\nexport function openBlock(): void {\n  currentBlock = [];\n}\n\nexport function createBlock(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode = createVNode(type, props, children, patchFlag, dynamicProps);\n\n  // 设置收集的动态节点\n  vnode.dynamicChildren = currentBlock;\n  currentBlock = null;\n\n  return vnode;\n}\n```\n\n### 收集动态节点\n\n在 `createVNode` 中，有 patchFlag 的 VNode 会被添加到 currentBlock．\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children,\n    patchFlag: patchFlag || 0,\n    dynamicProps: dynamicProps || null,\n    dynamicChildren: null,\n    // ...\n  };\n\n  // 有 patchFlag = 动态节点\n  // 如果 currentBlock 存在则添加\n  if (patchFlag !== undefined && patchFlag > 0 && currentBlock) {\n    currentBlock.push(vnode);\n  }\n\n  return vnode;\n}\n```\n\n## 生成的代码\n\n### 模板\n\n```vue\n<template>\n  <div>\n    <h1>Static Title</h1>\n    <p>{{ message }}</p>\n    <span :class=\"cls\">{{ text }}</span>\n  </div>\n</template>\n```\n\n### 生成的渲染函数\n\n```js\nimport { openBlock, createBlock, createVNode, toDisplayString } from 'vue'\n\n// 静态节点提升到外部\nconst _hoisted_1 = createVNode(\"h1\", null, \"Static Title\")\n\nfunction render(_ctx) {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // 静态（不包含在 dynamicChildren 中）\n      createVNode(\"p\", null, toDisplayString(_ctx.message), 1 /* TEXT */),\n      createVNode(\"span\", { class: _ctx.cls }, toDisplayString(_ctx.text), 3 /* TEXT | CLASS */)\n    ])\n  )\n}\n\n// 结果 VNode：\n// {\n//   type: \"div\",\n//   children: [_hoisted_1, p, span],\n//   dynamicChildren: [p, span]  // 只有动态节点\n// }\n```\n\n## patchBlockChildren 实现\n\n更新 Block 时，只遍历 `dynamicChildren`．\n\n```ts\nfunction patchBlockChildren(\n  oldChildren: VNode[],\n  newChildren: VNode[],\n  container: RendererElement,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  for (let i = 0; i < newChildren.length; i++) {\n    const oldVNode = oldChildren[i];\n    const newVNode = newChildren[i];\n\n    // 只 patch 动态节点\n    patch(oldVNode, newVNode, container, null, parentComponent);\n  }\n}\n```\n\n### 在 patchElement 中的使用\n\n```ts\nfunction patchElement(\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  const el = (n2.el = n1.el!);\n  const oldProps = n1.props || {};\n  const newProps = n2.props || {};\n\n  // 使用 patchFlag 的优化 patch\n  const { patchFlag, dynamicChildren } = n2;\n\n  if (patchFlag > 0) {\n    // 基于 patchFlag 只更新特定属性\n    if (patchFlag & PatchFlags.CLASS) {\n      patchClass(el, newProps.class);\n    }\n    if (patchFlag & PatchFlags.STYLE) {\n      patchStyle(el, oldProps.style, newProps.style);\n    }\n    if (patchFlag & PatchFlags.TEXT) {\n      if (n1.children !== n2.children) {\n        el.textContent = n2.children as string;\n      }\n    }\n    // ...\n  }\n\n  // 如果有 dynamicChildren，使用优化路径\n  if (dynamicChildren) {\n    patchBlockChildren(\n      n1.dynamicChildren!,\n      dynamicChildren,\n      el,\n      parentComponent\n    );\n  } else {\n    // 回退：普通子元素 patch\n    patchChildren(n1, n2, el, parentComponent);\n  }\n}\n```\n\n## 优化效果\n\n考虑 1000 个列表项中只更新 1 个的情况：\n\n- **完整 diff**：遍历 1000 个以上的节点\n- **仅 Patch Flags**：遍历 1000 个节点（属性比较被优化）\n- **Tree Flattening**：只遍历动态节点（1 个）\n\n动态节点越少，Tree Flattening 的效果就越大．\n\n## Block 失效的情况\n\n在以下情况下，Block 优化会被禁用（BAIL 模式）：\n\n1. **结构性指令**：`v-if`，`v-for` 创建新的 Block\n2. **动态组件**：`<component :is=\"...\">`\n3. **插槽出口**：`<slot />`\n\n```vue\n<template>\n  <div>\n    <!-- 这里 Block 被分割 -->\n    <div v-if=\"show\">\n      <p>{{ a }}</p>  <!-- Block A 的 dynamicChildren -->\n    </div>\n    <div v-else>\n      <p>{{ b }}</p>  <!-- Block B 的 dynamicChildren -->\n    </div>\n  </div>\n</template>\n```\n\n## 与 Static Hoisting 的集成\n\nTree Flattening 与 Static Hoisting 结合时效果最佳．\n\n```ts\n// 静态节点被提升，不包含在 dynamicChildren 中\nconst _hoisted_1 = createVNode(\"header\", null, [\n  createVNode(\"h1\", null, \"Title\"),\n  createVNode(\"nav\", null, [/* ... */])\n]);\n\nfunction render() {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // 静态：跳过\n      createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // 动态：追踪\n    ])\n  )\n}\n```\n\n1. **Static Hoisting**：将静态节点提升到函数外部（跳过 VNode 生成）\n2. **Tree Flattening**：只收集动态节点（限制 diff 目标）\n3. **Patch Flags**：只更新动态属性（优化属性比较）\n\n## 处理流程\n\n```\n[编译时]\n模板解析\n  ↓\n检测静态节点 → Static Hoisting\n  ↓\n检测动态节点 → 添加 Patch Flags\n  ↓\n识别 Block 边界 → 插入 openBlock/createBlock\n\n[运行时]\nopenBlock() → currentBlock = []\n  ↓\ncreateVNode (静态) → 不添加到 currentBlock\n  ↓\ncreateVNode (动态) → currentBlock.push(vnode)\n  ↓\ncreateBlock() → vnode.dynamicChildren = currentBlock\n\n[更新时]\npatchElement(n1, n2)\n  ↓\nn2.dynamicChildren 存在？\n  ↓ 是\npatchBlockChildren(n1.dynamicChildren, n2.dynamicChildren)\n  ↓\n只 patch 动态节点\n```\n\n## 总结\n\nTree Flattening（Block Tree）实现由以下部分组成：\n\n1. **dynamicChildren**：收集动态子节点的数组\n2. **openBlock / createBlock**：Block 创建和追踪\n3. **patchBlockChildren**：只 patch 动态节点\n4. **Block 边界管理**：用 `v-if`，`v-for` 等创建新 Block\n\n这个优化使 Vue 3 即使在大规模应用程序中也能实现快速更新．与 Static Hoisting 和 Patch Flags 结合时，可以实现基于模板的框架特有的优化．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/050-vapor/010-introduction.md",
    "content": "# Vapor 模式\n\n## 什么是 Vapor 模式？\n\nVapor 模式是 Vue.js 的一种新的编译策略，通过直接进行 DOM 操作而不使用虚拟 DOM 来提高性能．\n\n在传统的 Vue.js 中，当组件的状态发生变化时，会重新生成虚拟 DOM，进行差异检测（diffing），然后更新实际的 DOM．在 Vapor 模式中，消除了虚拟 DOM 的开销，当响应式值发生变化时，只直接执行必要的 DOM 操作．\n\n## 详细资源\n\n关于 Vapor 模式的详细解释，请参阅以下仓库：\n\n**[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor)**\n\n该仓库提供了 Vue.js Vapor 模式内部实现的深入解释．\n\n## chibivue 中的 Vapor 实现\n\nchibivue 在 `runtime-vapor` 包中提供了最小的 Vapor 实现．\n让我们看一个简单的实现来理解基本概念．\n\n### 基本思想\n\nVapor 模式的核心包括两点：\n\n1. **将模板直接转换为 DOM**：生成实际的 DOM 元素而不是虚拟 DOM 节点\n2. **将响应式值的变化直接反映到 DOM**：无需差异检测，只更新变化的部分\n\n### template 函数\n\n首先，让我们看看从 HTML 字符串创建 DOM 元素的 `template` 函数：\n\n```ts\nexport type VaporNode = Element & { __is_vapor: true };\n\nexport const template = (tmp: string): VaporNode => {\n  const container = document.createElement(\"div\");\n  container.innerHTML = tmp;\n  const el = container.firstElementChild as VaporNode;\n  el.__is_vapor = true;\n  return el;\n};\n```\n\n这个函数接收一个 HTML 字符串并返回一个实际的 DOM 元素．它直接操作 DOM，而不经过虚拟 DOM．\n\n### setText 函数\n\n`setText` 函数用于更新文本内容：\n\n```ts\nexport const setText = (\n  target: Element,\n  format: string,\n  ...values: any[]\n): void => {\n  const fmt = (): string => {\n    let text = format;\n    for (let i = 0; i < values.length; i++) {\n      text = text.replace(\"{}\", values[i]);\n    }\n    return text;\n  };\n\n  if (!target) return;\n\n  if (!values.length) {\n    target.textContent = fmt();\n    return;\n  }\n\n  if (!format && values.length) {\n    target.textContent = values.join(\"\");\n    return;\n  }\n\n  target.textContent = fmt();\n};\n```\n\n当响应式值发生变化时，会调用这个函数，直接更新 DOM 的文本内容．\n\n### on 函数\n\n`on` 函数用于注册事件监听器：\n\n```ts\nexport const on = (\n  element: Element,\n  event: string,\n  callback: () => void\n): void => {\n  element.addEventListener(event, callback);\n};\n```\n\n### Vapor 组件\n\nVapor 模式中的组件与普通的 Vue 组件形式不同：\n\n```ts\nexport type VaporComponent = (self: VaporComponentInternalInstance) => VaporNode;\n\nexport interface VaporComponentInternalInstance {\n  __is_vapor: true;\n  uid: number;\n  type: VaporComponent;\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null;\n  appContext: AppContext;\n  provides: Data;\n  isMounted: boolean;\n  // 生命周期钩子\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  // ...\n}\n```\n\nVapor 组件是一个接收实例并返回 VaporNode（实际的 DOM 元素）的函数．\n\n### 编译结果对比\n\n传统的基于虚拟 DOM 的编译结果：\n\n```ts\n// 输入: <div>{{ count }}</div>\n// 虚拟 DOM 输出\nfunction render(_ctx) {\n  return h(\"div\", null, _ctx.count);\n}\n```\n\nVapor 模式的编译结果：\n\n```ts\n// 输入: <div>{{ count }}</div>\n// Vapor 输出\nconst t0 = template(\"<div></div>\");\n\nfunction render(_ctx) {\n  const el = t0();\n  effect(() => {\n    setText(el, _ctx.count);\n  });\n  return el;\n}\n```\n\n在 Vapor 模式中：\n- 模板被预先生成为 DOM 元素（使用 `template` 函数）\n- 响应式值的更新在 `effect` 内直接操作 DOM\n- 没有虚拟 DOM 生成和差异检测的成本\n\n## 总结\n\nVapor 模式是一种通过消除虚拟 DOM 开销来提高性能的新方法．chibivue 的 `runtime-vapor` 包提供了这个概念的最小实现．\n\n有关更详细的实现和 Vue.js 官方的 Vapor 模式，请参阅 [reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor)．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/050-vapor/020-vapor-compiler.md",
    "content": "# Vapor 编译器\n\n在上一节中，我们了解了构成 Vapor 模式基础的运行时函数（`template`，`setText`，`on`）．\n在本节中，让我们实现一个编译器，它可以从模板自动生成使用这些函数的代码．\n\n## Vapor 编译器的目标\n\nVapor 编译器的目标是将这样的模板：\n\n```html\n<button @click=\"count++\">{{ count }}</button>\n```\n\n转换成这样的代码：\n\n```ts\nimport { template as _template, setText as _setText, on as _on, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  _renderEffect(() => {\n    _setText(_el0, \"\", count.value);\n  });\n  _on(_root, \"click\", () => count++);\n  return _root;\n})\n```\n\n关键点是：\n\n1. **静态部分变成模板字符串**：HTML 结构被预先创建为字符串\n2. **动态部分通过 renderEffect 处理**：响应式值的变化触发直接的 DOM 更新\n3. **事件处理器直接附加**：没有虚拟 DOM 事件委托\n\n## 编译器架构\n\nVapor 编译器遵循与常规模板编译器类似的流程，但采用了更精细的中间表示（IR）方法：\n\n```\n模板 (string)\n  ↓ [Parse]\nAST (抽象语法树)\n  ↓ [Transform]\nIR (中间表示)\n  ↓ [Codegen]\nVapor 代码 (string)\n```\n\n## 什么是 IR（中间表示）？\n\nIR（中间表示）是位于 AST 和最终代码之间的数据结构．\n使用 IR 的好处包括：\n\n1. **关注点分离**：清晰地分离解析和代码生成\n2. **易于优化**：在 IR 层面更容易进行静态分析和优化\n3. **可扩展性**：添加新功能更加简单\n\n### IR 结构\n\n```ts\n// IR 节点类型\nenum IRNodeTypes {\n  ROOT = \"root\",\n  BLOCK = \"block\",\n  SET_TEXT = \"setText\",\n  SET_EVENT = \"setEvent\",\n  SET_PROP = \"setProp\",\n  IF = \"if\",\n  FOR = \"for\",\n}\n\n// Block IR 节点 - 操作和效果的容器\ninterface BlockIRNode {\n  type: IRNodeTypes.BLOCK;\n  node: RootNode | TemplateChildNode;\n  dynamic: IRDynamicInfo;\n  effect: IREffect[];      // 响应式操作\n  operation: OperationNode[]; // 非响应式操作\n  returns: number[];       // 要返回的元素 ID\n}\n\n// Effect - 响应式依赖和操作的集合\ninterface IREffect {\n  expressions: SimpleExpressionNode[]; // 依赖的表达式\n  operations: OperationNode[];         // 要执行的操作\n}\n```\n\n### 操作节点示例\n\n```ts\n// 文本更新\ninterface SetTextIRNode {\n  type: IRNodeTypes.SET_TEXT;\n  element: number;  // 元素 ID\n  values: SimpleExpressionNode[];\n}\n\n// 事件绑定\ninterface SetEventIRNode {\n  type: IRNodeTypes.SET_EVENT;\n  element: number;\n  key: string;      // 事件名\n  value: SimpleExpressionNode;\n  modifiers?: string[];\n}\n\n// 属性绑定\ninterface SetPropIRNode {\n  type: IRNodeTypes.SET_PROP;\n  element: number;\n  key: string;\n  value: SimpleExpressionNode;\n}\n```\n\n## Transformer 的作用\n\nTransformer 将 AST 转换为 IR．\n它遍历每个 AST 节点并生成适当的 IR 节点．\n\n### TransformContext\n\n```ts\ninterface TransformContext {\n  root: RootIRNode;\n  block: BlockIRNode;\n  template: string;        // 静态模板字符串\n  elementCount: number;\n\n  // 分配元素 ID\n  reference(): number;\n\n  // 注册响应式效果\n  registerEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void;\n\n  // 注册非响应式操作\n  registerOperation(...operations: OperationNode[]): void;\n\n  // 进入新块（用于 v-if, v-for）\n  enterBlock(block: BlockIRNode): () => void;\n}\n```\n\n### 转换流程\n\n```ts\nexport function transform(ast: RootNode, source: string): RootIRNode {\n  const ir = createRootIR(ast, source);\n  const context = createTransformContext(ir);\n\n  // 递归转换子元素\n  transformChildren(ast.children, context);\n\n  // 保存模板字符串\n  ir.template.push(context.template);\n\n  return ir;\n}\n```\n\n### 指令转换\n\n每个指令由专用的转换函数处理：\n\n```ts\n// v-on 转换\nfunction transformVOn(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const eventName = (dir.arg as SimpleExpressionNode).content;\n\n  // 事件注册为操作（非响应式）\n  context.registerOperation({\n    type: IRNodeTypes.SET_EVENT,\n    element: elementId,\n    key: eventName,\n    value: dir.exp as SimpleExpressionNode,\n    modifiers: dir.modifiers,\n  });\n}\n\n// v-bind 转换\nfunction transformVBind(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const propName = (dir.arg as SimpleExpressionNode).content;\n\n  // 注册为效果（响应式）\n  context.registerEffect([dir.exp as SimpleExpressionNode], [\n    {\n      type: IRNodeTypes.SET_PROP,\n      element: elementId,\n      key: propName,\n      value: dir.exp as SimpleExpressionNode,\n    },\n  ]);\n}\n```\n\n### 常量表达式优化\n\n`registerEffect` 检查表达式是否为常量，如果是，则将其注册为普通 `operation` 而不是 `effect`：\n\n```ts\nregisterEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void {\n  // 过滤掉常量表达式\n  const reactiveExpressions = expressions.filter((exp) => !isConstantExpression(exp));\n\n  // 如果没有响应式依赖，注册为操作\n  if (reactiveExpressions.length === 0) {\n    context.registerOperation(...operations);\n    return;\n  }\n\n  // 注册为效果\n  currentBlock.effect.push({\n    expressions: reactiveExpressions,\n    operations,\n  });\n}\n```\n\n## 什么是 renderEffect？\n\n`renderEffect` 是 Vapor 模式的核心函数．\n与虚拟 DOM 基于差异的方法不同，它直接跟踪响应式依赖并在变化时更新 DOM．\n\n### 工作原理\n\n```ts\n/**\n * renderEffect - Vapor 模式中响应式 DOM 更新的核心机制\n *\n * 1. 将 DOM 更新函数包装在响应式效果中\n * 2. 自动跟踪访问了哪些响应式值\n * 3. 当跟踪的值发生变化时重新运行更新函数\n * 4. 只更新需要更改的特定 DOM 节点\n *\n * 重要：renderEffect 还处理生命周期钩子：\n * - 在每次更新前调用 onBeforeUpdate 钩子（初始挂载后）\n * - 在每次更新后调用 onUpdated 钩子（初始挂载后）\n */\nexport const renderEffect = (fn: () => void): void => {\n  const instance = currentInstance;\n\n  effect(() => {\n    // 更新前：调用 onBeforeUpdate 钩子（仅在挂载后）\n    if (instance?.isMounted) {\n      const { bu } = instance;\n      if (bu) invokeArrayFns(bu);\n    }\n\n    // 执行更新\n    fn();\n\n    // 更新后：调用 onUpdated 钩子（仅在挂载后）\n    if (instance?.isMounted) {\n      const { u } = instance;\n      if (u) {\n        queueMicrotask(() => invokeArrayFns(u));\n      }\n    }\n  });\n};\n```\n\n### 生成的代码示例\n\n```ts\n// 模板: <span>{{ count }}</span>\n\nrenderEffect(() => {\n  setText(_el0, \"\", count.value)\n})\n\n// 当 count.value 变化时：\n// 1. 调用 onBeforeUpdate 钩子\n// 2. 更新文本内容\n// 3. 调用 onUpdated 钩子（在微任务中）\n```\n\n### 与虚拟 DOM 的比较\n\n| 方面 | 虚拟 DOM | Vapor (renderEffect) |\n|------|----------|---------------------|\n| 更新粒度 | 重新渲染整个组件 | 只更新变化的部分 |\n| 跟踪方法 | 差异算法 | 响应式依赖跟踪 |\n| 开销 | VNode 创建和比较 | 无（直接 DOM 操作） |\n\n## Codegen 实现\n\nCodegen 从 IR 生成代码：\n\n```ts\nexport function generateVaporFromIR(ir: RootIRNode, options = {}): VaporCodegenResult {\n  const context = createVaporCodegenContext();\n\n  // 生成前导（导入等）\n  genVaporPreamble(context, options.isBrowser);\n\n  // 生成组件函数\n  push(`((_self) => {`);\n  indent();\n\n  // 生成 template() 调用\n  push(`const _root = _template(\\`${ir.template[0]}\\`);`);\n\n  // 生成元素引用\n  for (let i = 0; i < elementCount; i++) {\n    push(`const _el${i} = _root${generateElementPath(i)};`);\n  }\n\n  // 生成非响应式操作\n  for (const op of block.operation) {\n    genOperation(op, context);\n  }\n\n  // 生成响应式效果\n  for (const effect of block.effect) {\n    push(`_renderEffect(() => {`);\n    indent();\n    for (const op of effect.operations) {\n      genOperation(op, context);\n    }\n    deindent();\n    push(`});`);\n  }\n\n  push(`return _root;`);\n  deindent();\n  push(`})`);\n\n  return { code: context.code, preamble, ast: ir.node };\n}\n```\n\n## 使用示例\n\n```ts\nimport { compile } from \"@chibivue/compiler-vapor\";\n\n// 基于 IR 的编译\nconst result = compile(`\n  <button @click=\"count++\" :class=\"btnClass\">Count: {{ count }}</button>\n`, { useIR: true });\n\nconsole.log(result.code);\n```\n\n输出：\n\n```ts\nimport { template as _template, setText as _setText, on as _on, setClass as _setClass, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  const _el1 = _root;\n  _on(_el1, \"click\", count++);\n  _renderEffect(() => {\n    _setClass(_el1, btnClass);\n  });\n  _renderEffect(() => {\n    _setText(_el0, \"\", count);\n  });\n  return _root;\n})\n```\n\n## 总结\n\nVapor 编译器通过以下流程将模板转换为代码：\n\n1. **Parse**：将模板转换为 AST\n2. **Transform**：将 AST 转换为 IR（包括优化）\n3. **Codegen**：从 IR 生成代码\n\n使用 IR 可以：\n- 更容易进行静态分析和优化\n- 提高代码可维护性\n- 简化新功能的添加\n\n使用 `renderEffect`：\n- 可以进行细粒度的响应式更新\n- 消除虚拟 DOM 开销\n- 只有更改的部分才会被有效更新\n- 自动处理生命周期钩子（onBeforeUpdate，onUpdated）\n\n在下一节中，我们将了解如何使用 SSR 支持在服务器上渲染 Vapor 组件．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/90-web-application-essentials/050-vapor/030-vapor-ssr.md",
    "content": "# Vapor SSR\n\n在本节中，我们将探讨如何在服务器端渲染 Vapor 组件．\n由于 Vapor 组件直接操作 DOM，而服务器上不存在 DOM，因此 Vapor 的 SSR（服务器端渲染）面临独特的挑战．\n\n## 挑战\n\nVapor 组件的工作方式：\n1. 使用 `document.createElement` 创建 DOM 元素（通过 `template()`）\n2. 使用 `textContent`，`addEventListener` 等直接操作这些元素\n\n在服务器上，没有 `document` 对象．我们需要一种不同的方法来从 Vapor 组件生成 HTML 字符串．\n\n## 解决方案\n\nVapor SSR 有两种主要方法：\n\n1. **Mock DOM**：创建一个捕获操作并将其转换为 HTML 的假 DOM 环境\n2. **重用 VNode SSR**：在服务器端使用标准的 VNode 基础 SSR，在客户端作为 Vapor 进行水合\n\nVue.js 的 [PR #13226](https://github.com/vuejs/core/pull/13226) 采用了第二种方法．chibivue 也实现了类似的方法．\n\n<KawaikoNote variant=\"base\" title=\"Vue.js 的方法\">\nVue.js 的 Vapor SSR 在服务器端使用现有的 VNode 基础 SSR（compiler-ssr），在客户端使用 `createVaporSSRApp` 进行水合。这消除了创建单独 SSR 编译器的需要。\n</KawaikoNote>\n\n## 实现方式\n\n### 服务器端：使用 VNode SSR\n\n在 Vapor SSR 中，Vapor 组件在服务器端被编译为常规的 VNode 基础组件．这允许直接使用 `@chibivue/compiler-ssr`．\n\n```ts\n// compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  ssr = false,\n  vapor = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  // 即使在 Vapor + SSR 模式下也使用 compiler-ssr\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = defaultCompiler.compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n\n  // 在 Vapor + SSR 模式下添加 __vapor 标志\n  if (vapor && ssr) {\n    code = code.replace(\n      /export (function|const) ssrRender/,\n      \"export const __vapor = true;\\nexport $1 ssrRender\",\n    );\n  }\n\n  return { code, ast, source, preamble };\n}\n```\n\n`__vapor` 标志表示在水合时应使用 Vapor 模式．\n\n### 客户端：createVaporSSRApp\n\n在客户端，使用 `createVaporSSRApp` 来水合 SSR 渲染的 HTML．\n\n```ts\n// runtime-vapor/src/apiCreateVaporApp.ts\nexport function createVaporSSRApp(rootComponent: VaporComponent): VaporApp {\n  const context = createAppContext();\n\n  const app: VaporApp = {\n    // ... 通用应用配置 ...\n\n    mount(containerOrSelector: Element | string) {\n      const container = typeof containerOrSelector === \"string\"\n        ? document.querySelector(containerOrSelector)\n        : containerOrSelector;\n\n      if (container?.hasChildNodes()) {\n        // 当存在 SSR 内容时进入水合模式\n        const vnode = createVNode(rootComponent as any);\n        vnode.appContext = context;\n        const instance = hydrateVaporComponent(vnode, container, null);\n        app._instance = instance;\n      } else {\n        // 没有 SSR 内容时进行正常挂载\n        // ...\n      }\n    },\n  };\n\n  return app;\n}\n```\n\n### 水合\n\n水合过程重用现有的 DOM 元素，同时设置响应性和事件监听器．\n\n```ts\n// runtime-vapor/src/hydration.ts\nexport function hydrateVaporComponent(\n  vnode: VNode,\n  container: Element,\n  parentInstance: VaporComponentInternalInstance | null = null,\n): VaporComponentInternalInstance {\n  const instance = createVaporComponentInstance(vnode, parentInstance);\n\n  // 设置水合上下文\n  const ctx: VaporHydrationContext = {\n    node: container.firstChild,\n    parent: container,\n  };\n\n  setCurrentInstance(instance as any);\n  (instance as any).__hydrationCtx = ctx;\n\n  try {\n    const comp = instance.type as VaporComponent;\n    // 执行组件 - template() 找到现有的 DOM\n    const el = comp(instance);\n\n    // 标记为已挂载\n    instance.isMounted = true;\n\n    // 调用 mounted 钩子\n    const { m } = instance as any;\n    if (m) invokeArrayFns(m);\n\n    return instance;\n  } finally {\n    unsetCurrentInstance();\n    delete (instance as any).__hydrationCtx;\n  }\n}\n```\n\n## Mock DOM 方法\n\nchibivue 也在 `server-renderer` 中实现了 Mock DOM 方法．当不使用 VNode SSR 时，这可以作为后备方案．\n\n### SSR 元素\n\n我们创建模仿 DOM 元素但将数据存储在内存中的类：\n\n```ts\nclass SSRElement {\n  tagName: string;\n  attributes: Map<string, string> = new Map();\n  children: (SSRElement | SSRText)[] = [];\n  textContent: string = \"\";\n\n  constructor(tagName: string) {\n    this.tagName = tagName.toLowerCase();\n  }\n\n  setAttribute(name: string, value: string): void {\n    this.attributes.set(name, value);\n  }\n\n  addEventListener(): void {\n    // SSR 中不做任何操作 - 事件仅在客户端\n  }\n\n  appendChild(child: SSRElement | SSRText): void {\n    this.children.push(child);\n  }\n\n  toHTML(): string {\n    let html = `<${this.tagName}`;\n    for (const [name, value] of this.attributes) {\n      html += ` ${name}=\"${escapeHtml(value)}\"`;\n    }\n    html += \">\";\n\n    if (this.textContent) {\n      html += escapeHtml(this.textContent);\n    } else {\n      for (const child of this.children) {\n        html += child.toHTML();\n      }\n    }\n\n    html += `</${this.tagName}>`;\n    return html;\n  }\n}\n```\n\n## 使用示例\n\n### 服务器端\n\n```ts\nimport { createVNode } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport App from \"./App.vue\";\n\n// 将组件渲染为 HTML 字符串\nconst html = await renderToString(createVNode(App));\n\n// 发送 HTML 响应\nres.send(`\n<!DOCTYPE html>\n<html>\n  <head><title>My App</title></head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/entry-client.ts\"></script>\n  </body>\n</html>\n`);\n```\n\n### 客户端\n\n```ts\n// entry-client.ts\nimport { createVaporSSRApp } from \"@chibivue/runtime-vapor\";\nimport App from \"./App.vue\";\n\n// 水合 SSR 渲染的 HTML\ncreateVaporSSRApp(App).mount(\"#app\");\n```\n\n## 与虚拟 DOM SSR 的比较\n\n| 方面 | 虚拟 DOM SSR | Vapor SSR |\n|--------|-----------------|-----------|\n| 服务器渲染 | 遍历 VNode 树，生成 HTML | 相同（使用 VNode SSR） |\n| 客户端水合 | 使用 VNode diff | 直接引用/操作 DOM |\n| 包大小 | 需要虚拟 DOM 运行时 | 轻量级 Vapor 运行时 |\n| 更新性能 | 经过 diff 算法 | 直接 DOM 操作 |\n\n## 架构优势\n\nVue.js 风格的 Vapor SSR 方法具有以下优势：\n\n1. **代码重用**：可以直接使用现有的 `compiler-ssr`\n2. **一致的输出**：服务器生成的 HTML 与常规 VNode SSR 相同\n3. **渐进式迁移**：可以与非 Vapor 组件共存\n4. **可维护性**：无需维护单独的 SSR 编译器\n\n<KawaikoNote variant=\"warning\" title=\"需要水合\">\n服务器渲染的 HTML 是静态的。为了获得交互性，你需要在客户端水合 Vapor 组件，这将设置响应式 effect 和事件监听器。\n</KawaikoNote>\n\n## 限制\n\n当前实现是最小的，有一些限制：\n\n1. **不支持流式传输**：整个组件在返回之前被渲染\n2. **不支持 Suspense**：异步组件的 SSR 支持有限\n3. **水合不匹配**：客户端和服务器输出不同时的警告功能未实现\n\n<KawaikoNote variant=\"base\" title=\"未来改进\">\n更完整的实现将包括：\n- 流式 SSR 支持\n- 水合不匹配检测\n- Suspense 集成\n</KawaikoNote>\n\n## 总结\n\nVapor SSR 的工作方式如下：\n\n1. **服务器端**：使用 `compiler-ssr` 生成 HTML 字符串（与 VNode SSR 相同）\n2. **客户端**：使用 `createVaporSSRApp` 进行水合\n3. **水合**：重用现有的 DOM 元素，同时设置响应性\n\n这种方法允许 Vapor 组件享受 SSR 的好处，同时在客户端获得直接 DOM 操作的性能优势．\n"
  },
  {
    "path": "book/online-book/src/zh-cn/bonus/debug-vuejs-core.md",
    "content": "# 调试原始源代码\n\n有时您可能想要运行和测试 Vue.js 的实际源代码．  \n作为本书方法的一部分，我们强烈建议阅读和理解原始源代码，以及进行源代码阅读和测试实验．\n\n因此，我们将介绍几种调试原始源代码的方法，这些方法在正文中没有涉及．\n\n（我们将按照易于理解的顺序介绍它们．）\n\n## 利用 SFC Playground\n\n这是最简单的方法．它广为人知，甚至在官方文档中也有链接．\n\nhttps://play.vuejs.org\n\n在这个 playground 中，您不仅可以编写 Vue 组件并检查它们的行为，还可以检查 SFC 的编译结果．  \n它很方便，因为您可以在浏览器中快速检查它．（当然，您也可以分享它．）\n\n<video src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/8281e589-fdaf-4206-854e-25a66dfaac05\" controls />\n\n## 利用 vuejs/core 测试\n\n接下来，让我们尝试运行 [vuejs/core](https://github.com/vuejs/core) 的测试．\n当然，您需要克隆 [vuejs/core](https://github.com/vuejs/core) 的源代码．\n\n```bash\ngit clone https://github.com/vuejs/core.git vuejs-core\n# NOTE: 建议使其易于理解，因为仓库名称是 `core`\n```\n\n然后，\n\n```bash\ncd vuejs-core\nni\npnpm test\n```\n\n您可以运行测试，所以请随意修改您感兴趣的源代码并运行测试．\n\n除了 `test` 之外还有几个其他的测试命令，如果您感兴趣，请检查 `package.json`．\n\n您可以阅读和理解测试代码，修改代码并运行测试，或添加测试用例．有各种使用方法．\n\n<img width=\"590\" alt=\"Screenshot 2024-01-07 0 31 29\" src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/3c862bd5-1d94-4d2a-a9fa-8755872098ed\">\n\n## 运行 vuejs/core 源代码\n\n接下来，这是最方便但仍然是实际修改和运行 vuejs/core 源代码的方法．\n\n关于这一点，我们已经准备了可以与 vite 进行 HMR 的项目，包括 SFC 和独立版本，所以请尝试使用它们．\n这个项目在 [chibivue](https://github.com/chibivue-land/chibivue) 的仓库中，所以请克隆它．\n\n```bash\ngit clone https://github.com/chibivue-land/chibivue.git\n```\n\n克隆后，运行脚本来创建项目．\n\n此时，您应该被要求输入本地 vuejs/core 源代码的**绝对路径**，所以请输入它．\n\n```bash\ncd chibivue\nni\npnpm setup:vue\n\n# 💁 input your local vuejs/core absolute path:\n#   e.g. /Users/ubugeeei/oss/vuejs-core\n#   >\n```\n\n这将在 chibivue 仓库中创建一个指向本地 vuejs/core 源代码的 Vue 项目．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/5d57c022-c411-4452-9e7e-c27623ec28b4\" controls/>\n\n然后，当您想要启动时，您可以使用以下命令启动它，并在修改 vuejs/core 源代码的同时检查操作．\n\n```bash\npnpm dev:vue\n```\n\n当然，playground 端的 HMR，\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/a2ad46d8-4b07-4ac5-a887-f71507c619a6\" controls/>\n\n即使您修改 vuejs/core 代码，HMR 也会工作．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/72f38910-19b8-4171-9ed7-74d1ba223bc8\" controls/>\n\n---\n\n另外，如果您想在独立模式下检查它，您也可以通过将 index.html 更改为加载 standalone-vue.js 来使用 HMR．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/c57ab5c2-0e62-4971-b1b4-75670d3efeec\" controls/>\n"
  },
  {
    "path": "book/online-book/src/zh-cn/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl.md",
    "content": "# Hyper Ultimate Super Extreme Minimal Vue\n\n## 项目设置（0.5 分钟）\n\n```sh\n# 克隆此仓库并导航到它。\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\n\n# 使用设置命令创建项目。\n# 将项目的根路径指定为参数。\npnpm setup ../my-chibivue-project\n```\n\n项目设置现在完成了．\n\n让我们现在实现 packages/index.ts．\n\n## createApp（1 分钟）\n\n对于 create app 函数，让我们考虑一个允许指定 setup 和 render 函数的签名．从用户的角度来看，它将这样使用：\n\n```ts\nconst app = createApp({\n  setup() {\n    // TODO:\n  },\n  render() {\n    // TODO:\n  },\n})\n\napp.mount('#app')\n```\n\n让我们实现它：\n\n```ts\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\n```\n\n然后我们可以返回一个实现 mount 函数的对象：\n\n```ts\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    // TODO: patch rendering\n  },\n})\n```\n\n这部分就是这样．\n\n## h 函数和虚拟 DOM（0.5 分钟）\n\n要执行补丁渲染，我们需要虚拟 DOM 和生成它的函数．\n\n虚拟 DOM 使用 JavaScript 对象表示标签名称，属性和子元素．Vue 渲染器处理虚拟 DOM 并将更新应用到实际 DOM．\n\n让我们考虑一个 VNode，它表示一个名称，一个点击事件处理程序和子元素（文本）：\n\n```ts\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n```\n\n这部分就是这样．\n\n## 补丁渲染（2 分钟）\n\n现在让我们实现渲染器．\n\n这个渲染过程通常被称为补丁，因为它比较旧的和新的虚拟 DOM 并将差异应用到实际 DOM．\n\n函数签名将是：\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  // TODO:\n}\n```\n\nn1 表示旧的 VNode，n2 表示新的 VNode，container 是实际 DOM 的根．在这个例子中，`#app` 将是容器（使用 createApp 挂载的元素）．\n\n我们需要考虑两种类型的操作：\n\n- 挂载  \n  这是初始渲染．如果 n1 为 null，意味着这是第一次渲染，所以我们需要实现挂载过程．\n- 补丁  \n  这比较 VNode 并将差异应用到实际 DOM．  \n  但是这次，我们只更新子元素而不检测差异．\n\n让我们实现它：\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n```\n\n这部分就是这样．\n\n## 响应式系统（2 分钟）\n\n现在让我们实现逻辑来跟踪在 setup 选项中定义的状态变化并触发 render 函数．这个跟踪状态变化并执行特定操作的过程称为\"响应式系统\"．\n\n让我们考虑使用 `reactive` 函数来定义状态：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n  // ..\n  // ..\n})\n```\n\n在这种情况下，当使用 `reactive` 函数定义的状态被修改时，我们希望触发补丁过程．\n\n它可以使用 Proxy 对象来实现这一点．代理允许我们为 get/set 操作实现功能．在这种情况下，我们可以使用 set 操作在发生 set 操作时执行补丁过程．\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      // ??? 这里我们想要执行补丁过程\n      return res\n    },\n  })\n```\n\n问题是，我们应该在 set 操作中触发什么？通常，我们会使用 get 操作来跟踪变化，但在这种情况下，我们将在全局范围内定义一个 `update` 函数并引用它．\n\n让我们使用之前实现的 render 函数来创建 update 函数：\n\n```ts\nlet update: (() => void) | null = null // 我们想要用 Proxy 引用这个，所以它需要在全局范围内\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup() // 只在第一次渲染时运行 setup\n    update = () => {\n      // 生成一个闭包来比较 prevVNode 和 VNode\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n```\n\n现在我们只需要在 Proxy 的 set 操作中调用它：\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.() // 执行更新\n      return res\n    },\n  })\n```\n\n就是这样！\n\n## 模板编译器（5 分钟）\n\n到目前为止，我们已经能够通过允许用户使用 render 选项和 h 函数来实现声明式 UI．但是，实际上，我们希望以类似 HTML 的方式编写它．\n\n因此，让我们实现一个模板编译器，将 HTML 转换为 h 函数．\n\n目标是将这样的字符串：\n\n```\n<button @click=\"increment\">state: {{ state.count }}</button>\n```\n\n转换为这样的函数：\n\n```\nh(\"button\", increment, \"state: \" + state.count)\n```\n\n让我们稍微分解一下．\n\n- parse  \n  解析 HTML 字符串并将其转换为称为 AST（抽象语法树）的对象．\n- codegen  \n  基于 AST 生成所需的代码（字符串）．\n\n现在，让我们实现 AST 和 parse．\n\n```ts\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\n```\n\n我们这次处理的 AST 如上所示．它类似于 VNode，但完全不同，用于生成代码．Interpolation 表示胡须语法．像 <span v-pre>`{{ state.count }}`</span> 这样的字符串被解析为像 <span v-pre>`{ content: \"state.count\" }`</span> 这样的对象（AST）．\n\n接下来，让我们实现从给定字符串生成 AST 的 parse 函数．现在，让我们使用正则表达式和一些字符串操作快速实现它．\n\n```ts\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\n```\n\n接下来是 codegen．基于 AST 生成 h 函数的调用．\n\n```ts\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\n```\n\n状态从参数 `_ctx` 中引用．\n\n通过组合这些，我们可以完成 compile 函数．\n\n```ts\nconst compile = (template: string): string => codegen(parse(template))\n```\n\n好吧，实际上，就目前而言，它只是生成 h 函数调用的字符串，所以它还不能工作．\n\n我们将与 sfc 编译器一起实现它．\n\n有了这个，模板编译器就完成了．\n\n## sfc 编译器（vite-plugin）（4 分钟）\n\n最后！让我们实现一个 vite 插件来支持 sfc．\n\n在 vite 插件中，有一个名为 transform 的选项，它允许您转换文件的内容．\n\ntransform 函数返回类似 `{ code: string }` 的东西，字符串被视为源代码．换句话说，例如，\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: \"vite-plugin-chibivue\",\n  transform: (code: string, id: string) => ({\n    code: \"\";\n  }),\n});\n```\n\n将使所有文件的内容成为空字符串．原始代码可以作为第一个参数接收，所以通过正确转换这个值并在最后返回它，您可以转换它．\n\n有 5 件事要做．\n\n- 从脚本中提取作为默认导出的内容．\n- 将其转换为将其分配给变量的代码．（为了方便，让我们称变量为 A．）\n- 从模板中提取 HTML 字符串，并使用我们之前创建的 compile 函数将其转换为对 h 函数的调用．（为了方便，让我们称结果为 B．）\n- 生成类似 `Object.assign(A, { render: B })` 的代码．\n- 生成将 A 作为默认导出的代码．\n\n现在让我们实现它．\n\n```ts\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\n之后，在插件中实现它．\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : code, // 仅适用于 .vue 扩展名的文件\n})\n```\n\n## 结束\n\n是的．有了这个，我们已经成功实现到 SFC．\n让我们再看一下源代码．\n\n```ts\n// create app api\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\nlet update: (() => void) | null = null\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup()\n    update = () => {\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n\n// Virtual DOM patch\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n\n// Virtual DOM\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n\n// Reactivity System\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.()\n      return res\n    },\n  })\n\n// template compiler\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\nconst compile = (template: string): string => codegen(parse(template))\n\n// sfc compiler (vite transformer)\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : null,\n})\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\n令人惊讶的是，我们能够在大约 110 行中实现它．（现在没有人会抱怨了，呼...）\n\n请确保也尝试主要部分的主要部分！！（虽然这只是一个附录）\n"
  },
  {
    "path": "book/online-book/src/zh-cn/bonus/hyper-ultimate-super-extreme-minimal-vue/index.md",
    "content": "# chibivue？哪里小了！？太大了，我处理不了！\n\n## 它很大...\n\n对于那些这样想的人，我真诚地道歉．\n\n在拿起这本书之前，您可能想象的是更小的东西．\n\n请允许我稍作辩解，即使是我也没有打算把它做得这么大．\n\n当我继续工作时，我发现它很有趣，并想，\"哦，我下一步应该添加这个功能吗？\"就这样变成了这样．\n\n## 明白了．让我们设定一个时间限制．\n\n导致它变得太大的因素之一是\"没有时间限制\"．\n\n所以，在这个附录中，我将尝试在\"**15 分钟**\"内实现它．\n\n当然，我也会将解释限制在一页内．\n\n此外，不仅是页面，\"实现本身将包含在一个文件中\"也是我将尝试实现的目标．\n\n但是，即使是一个文件，在一个文件中写 100,000 行也是没有意义的，所以我将目标是在少于 150 行内实现它．\n\n![Full source of Hyper Ultimate Super Extreme Minimal Vue in one file](/figures/bonus/hyper-ultimate-super-extreme-minimal-vue/full-source-screenshot.png)\n\n标题是\"**Hyper Ultimate Super Extreme Minimal Vue**\"．\n\n::: info 关于名称\n\n我想很多人认为这个名字相当幼稚．\n\n我也这么认为．\n\n但是，这个名字有一个合适的理由．\n\n在强调它极其小的同时，我想要一个缩写，所以就变成了这个词序．\n\n缩写是\"HUSEM Vue (Balloon Vue)\"．\n\n\"HU-SEN\" [fuːsen] 在日语中意思是\"气球\"．\n\n虽然我现在将以一种非常草率的方式实现它，但我将这种草率比作一个\"气球\"，即使针碰到它也会爆炸．\n\n:::\n\n## 你只是要实现一个响应式系统，对吧？\n\n不，不是这样的．这次，我将尝试列出将在 15 分钟内实现的内容．\n\n- create app api\n- Virtual DOM\n- patch rendering\n- Reactivity System\n- template compiler\n- sfc compiler (vite-plugin)\n\n我将实现这些东西．\n\n换句话说，SFC 将工作．\n\n至于源代码，我假设以下内容将工作：\n\n```vue\n<script>\nimport { reactive } from 'hyper-ultimate-super-extreme-minimal-vue'\n\nexport default {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"increment\">state: {{ state.count }}</button>\n</template>\n```\n\n```ts\nimport { createApp } from 'hyper-ultimate-super-extreme-minimal-vue'\n\n// @ts-ignore\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n"
  },
  {
    "path": "book/online-book/src/zh-cn/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: \"chibivue\"\n  text: \"从一行 \\\"Hello, World\\\" 开始，逐步构建\"\n  tagline: 基于 VitePress 构建\n  image: /figures/_brand/logo.png\n  actions:\n    - theme: brand\n      text: 开始阅读 ->\n      link: /zh-cn/00-introduction/010-about\n    - theme: alt\n      text: Vue.js 官方文档\n      link: https://vuejs.org/\n\nfeatures:\n  - title: 响应式系统\n    details: 从响应式系统的基本原理开始，我们将涵盖广泛的实现，从 EffectScope 到 CustomRef 等高级 API。\n  - title: 虚拟 DOM\n    details: 我们将涵盖广泛的实现，从虚拟 DOM 的基本设置到补丁渲染和调度器实现。\n  - title: 模板编译器\n    details: 从模板编译器的基础实现开始，我们将扩展到数据绑定和指令实现。\n  - title: 单文件组件\n    details: 从 SFC 的基本实现开始，我们将深入广泛的领域，从 script setup 到编译器宏和作用域 CSS 实现。\n---\n"
  },
  {
    "path": "book/online-book/src/zh-tw/00-introduction/010-about.md",
    "content": "# 介紹\n\n## 本書的目的\n\n感謝您選擇這本書！  \n如果您對這本書哪怕有一點點興趣，我都感到非常高興．  \n讓我首先總結一下這本書的目的．\n\n**☆ 目的**\n\n- **深入理解 Vue.js**  \n  什麼是 Vue.js？它是如何構建的？\n- **能夠實現 Vue.js 的基本功能**  \n  實際嘗試實現基本功能．\n- **閱讀 vuejs/core 的源代碼**  \n  理解實現與官方代碼之間的關係，掌握它們是如何真正構建的．\n\n我提供了一個大致的目標概述，但沒有必要完成所有目標，也不是要求追求完美．  \n無論您是從頭到尾閱讀，還是只挑選感興趣的部分，都由您決定．  \n如果您發現這本書的哪怕一小部分有用，我都會很高興！\n\n## 目標讀者\n\n- **有 Vue.js 使用經驗的人**\n- **能夠編寫 TypeScript**\n\n僅有這兩個先決條件，不需要其他知識．  \n雖然您在閱讀本書過程中可能會遇到不熟悉的術語，但我已經盡力排除任何先驗知識，並在過程中進行解釋，旨在使這本書自成體系．  \n但是，如果您遇到不應該用於 Vue.js 或 TypeScript 的術語，我建議您先從相應的資源中學習．  \n（基本功能就足夠了！（不需要深入研究））\n\n## 本書（和作者）所關注的（並希望實現的）\n\n在深入之前，我想分享一些我在寫這本書時特別關注的事情．  \n我希望您在閱讀時記住這些，如果有任何我沒有達到目標的地方，請告訴我．\n\n- **消除對先驗知識的需求**  \n  雖然這可能與前面提到的「目標讀者」部分重疊，但我努力使這本書盡可能自成體系，  \n  最大限度地減少對先驗知識的需求，並根據需要提供解釋．  \n  這是因為我想讓盡可能多的讀者理解盡可能清晰的解釋．  \n  有豐富經驗的人可能會發現一些解釋有點冗長，但我請求您的理解．\n\n- **增量實現**  \n  本書的目標之一是手工增量實現 Vue.js．這意味著本書專注於實踐方法，  \n  在實現方面，我強調以小的增量步驟構建．  \n  更具體地說，就是「最小化非工作狀態」．  \n  而不是擁有直到完成才能工作的東西，目標是在每個階段都保持其功能．  \n  這反映了我個人的編碼方法——持續編寫非功能代碼可能令人沮喪．  \n  即使不完美，總是有東西在運行會使過程更愉快．  \n  這是關於體驗小勝利，比如「是的！現在它工作到這一點了！」\n\n- **避免對特定框架，庫或語言的偏見**  \n  雖然這本書專注於 Vue.js，但今天有無數優秀的框架，庫和語言．  \n  事實上，除了 Vue.js 之外，我還有我的最愛，我經常從用它們構建的見解和服務中受益．  \n  這本書的目的純粹是「理解 Vue.js」，不涉及對其他工具的排名或判斷．\n\n## 本線上書籍的主題和結構\n\n由於這本書變得相當龐大，我設置了成就里程碑並將其分為不同的部分．\n\n- **最小示例部分**  \n   在這裡，Vue.js 以最基本的形式實現．  \n   雖然這一部分涵蓋了最小的功能集，但它將處理  \n   虛擬 DOM，響應式系統，編譯器和 SFC（單文件組件）支持．  \n   然而，這些實現遠非實用，並且高度簡化．  \n   但是，對於那些想要 Vue.js 廣泛概述的人來說，這一部分提供了足夠的見解．  \n   作為介紹性部分，這裡的解釋比其他部分更詳細．  \n   在本部分結束時，讀者應該對閱讀官方 Vue.js 源代碼感到有些舒適．在功能上，您可以期望代碼大致執行以下操作...\n\n  ```vue\n  <script>\n  import { reactive } from 'chibivue'\n\n  export default {\n    setup() {\n      const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n      const changeMessage = () => {\n        state.message += '!'\n      }\n\n      const handleInput = e => {\n        state.input = e.target?.value ?? ''\n      }\n\n      return { state, changeMessage, handleInput }\n    },\n  }\n  </script>\n\n  <template>\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\">click me!</button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n    </div>\n  </template>\n\n  <style>\n  .container {\n    height: 100vh;\n    padding: 16px;\n    background-color: #becdbe;\n    color: #2c3e50;\n  }\n  </style>\n  ```\n\n  ```ts\n  import { createApp } from 'chibivue'\n  import App from './App.vue'\n\n  const app = createApp(App)\n\n  app.mount('#app')\n  ```\n\n- **基礎虛擬 DOM 部分**\n  在這一部分中，我們將為虛擬 DOM 實現相當實用的補丁渲染功能．雖然我們不會實現像 [Suspense](https://vuejs.org/guide/built-ins/suspense) 或其他優化等功能，但它將足夠熟練地處理基本渲染任務．我們還將在這裡實現調度器．\n\n- **基礎響應式系統部分**\n  雖然我們在最小示例部分實現了 reactive API，但在這一部分中我們將實現其他 API．從 ref，watch 和 computed 等基本 API 開始，我們還將深入研究 effectScope 和 shallow 系列等更高級的 API．\n\n- **基礎組件系統部分**\n  在這裡，我們將承擔與組件系統相關的基本實現．事實上，由於我們已經在基礎虛擬 DOM 部分為組件系統設置了基礎，這裡我們將專注於組件系統的其他方面．這包括 props/emit，provide/inject，響應式系統的擴展和生命週期鉤子等功能．\n\n- **基礎模板編譯器部分**\n  除了在基礎虛擬 DOM 部分實現的虛擬 DOM 系統編譯器之外，我們將實現 v-on，v-bind 和 v-for 等指令．通常，這將涉及組件的 template 選項，我們不會在這裡涵蓋 SFC（單文件組件）．\n\n- **基礎 SFC 編譯器部分**\n  在這裡，我們將在利用基礎模板編譯器部分實現的模板編譯器的同時，實現一個有些實用的 SFC 編譯器．\n  具體來說，我們將實現 script setup 和編譯器宏．\n  在這一點上，體驗將非常接近使用常規 Vue．\n\n- **Web 應用程式要點部分**\n  當我們完成基礎 SFC 編譯器部分時，我們將擁有一套有些實用的 Vue.js 功能．然而，要開發 Web 應用程式，仍然缺少很多東西．例如，我們需要管理全局狀態和路由器的工具．在這一部分中，我們將開發這樣的外部插件，旨在從「Web 應用程式開發」的角度使我們的工具包更加實用．\n\n## 關於本書的意見和問題\n\n我打算盡我所能回應關於這本書的問題和反饋．請隨時在 Twitter 上聯繫我（通過 DM 或直接在時間線上）．由於我已經公開了存儲庫，您也可以在那裡發布問題．我知道我自己的理解並不完美，所以我感謝任何反饋．如果您發現任何解釋不清楚或具有挑戰性，請不要猶豫詢問．我的目標是向盡可能多的人傳播清晰正確的解釋，我希望我們能夠一起構建這個．\n\nhttps://x.com/ubugeeei\n\n## 關於 Discord 伺服器\n\n我們為這本書創建了一個 Discord 伺服器！（2024/01/01）\n~~在這裡，我們分享公告，為與這本線上書籍相關的問題和技巧提供支持．~~ \\\n我們也歡迎隨意對話，所以讓我們與其他 chibivue 用戶愉快地交流．\n目前，由於有很多日語使用者，大部分對話都是日語，但非日語使用者也歡迎毫不猶豫地加入！（完全可以使用您的母語）\n\n最近，我們不僅積極為 chibivue 做貢獻，還作為 Vue.js 日本社群伺服器的一部分！\n\n### 我們大致做什麼\n\n- 自我介紹（可選）\n- 與 chibivue 相關的公告（如更新）\n- 分享技巧\n- 回答問題\n- 響應請求\n- 隨意對話\n\n### 如何加入\n\n這是邀請連結: https://discord.gg/aVHvmbmSRy\n\n您也可以從這本書標題右上角的 Discord 按鈕加入．\n\n## 關於作者\n\n**ubugeeei (もののけ王)**\n\n<img class=\"author-avatar\" src=\"/figures/_people/ubugeeei-avatar.jpg\" alt=\"ubugeeei\" width=\"160\" height=\"160\">\n\n[Vue.js](https://vuejs.org/about/team.html) Core Team 成員，[Vue.js Japan User Group](https://github.com/vuejs-jp) Core Staff，[Vite+](https://github.com/voidzero-dev/vite-plus) Core Contributor，[株式会社メイツ](https://github.com/mates-inc) Chief Engineer．\\\n[chibivue land](https://github.com/chibivue-land) King. https://chibivue.land\n\n我在圍繞 Vue，語言處理器和開發體驗製作工具與書籍，主要包括 [chibivue](https://github.com/chibivue-land/chibivue)，[Vize](https://github.com/ubugeeei/vize)，[Ox Content](https://github.com/ubugeeei/ox-content)，[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor) 和 [Vapor Moon](https://github.com/ubugeeei/vapor-moon)．\n\nhttps://wtrclred.io/\n\n如果您願意，請作為贊助商支持我！ https://github.com/sponsors/ubugeeei\n\n## 贊助商\n\n<div class=\"sponsors-block\">\n<a class=\"sponsors-image-link\" href=\"https://github.com/sponsors/ubugeeei\">\n  <img class=\"sponsors-image sponsors-image--light\" src=\"/figures/_sponsors/ubugeeei-sponsors.png\" alt=\"ubugeeei's sponsors\" />\n  <img class=\"sponsors-image sponsors-image--dark\" src=\"/figures/_sponsors/ubugeeei-sponsors-dark.png\" alt=\"ubugeeei's sponsors\" />\n</a>\n\n<p>如果您想支持我的工作，我將非常感激！</p>\n<p><a href=\"https://github.com/sponsors/ubugeeei\">https://github.com/sponsors/ubugeeei</a></p>\n\n</div>\n"
  },
  {
    "path": "book/online-book/src/zh-tw/00-introduction/020-what-is-vue.md",
    "content": "# 什麼是 Vue.js？\n\n## 關於 Vue.js 的快速回顧\n\n讓我們直奔主題．  \n但在此之前，讓我們快速回顧一下 Vue.js 的全部內容．\n\n## Vue.js 到底是什麼？\n\nVue.js 是一個「友好，高性能，多功能的構建 Web 用戶界面的框架」．  \n這是官方文檔主頁上的說明．  \n對此，我認為直接引用官方的話而不添加我自己的解釋會更清楚，所以我在下面引用了它們：\n\n> Vue（發音為 /vjuː/，類似 view）是一個用於構建用戶界面的 JavaScript 框架。它建立在標準 HTML、CSS 和 JavaScript 的基礎上，並提供了一套聲明式的、組件化的編程模型，幫助你高效地開發用戶界面，無論是簡單還是複雜的。\n\n> 聲明式渲染：Vue 基於標準 HTML 拓展了一套模板語法，使得我們可以聲明式地描述最終輸出的 HTML 和 JavaScript 狀態之間的關係。\n\n> 響應性：Vue 會自動跟蹤 JavaScript 狀態變化並在改變發生時響應式地更新 DOM。\n\n> 這裡是一個最小的示例：\n>\n> ```ts\n> import { createApp } from 'vue'\n>\n> createApp({\n>   data() {\n>     return {\n>       count: 0,\n>     }\n>   },\n> }).mount('#app')\n> ```\n>\n> ```html\n> <div id=\"app\">\n>   <button @click=\"count++\">Count is: {{ count }}</button>\n> </div>\n> ```\n\n[參考來源](https://vuejs.org/guide/introduction.html#what-is-vue)\n\n對於聲明式渲染和響應性，我們將在各自的章節中詳細深入探討，所以現在有一個高層次的理解就足夠了．\n\n![Vue.js as an implementation map](/figures/00-introduction/what-is-vue/vue-implementation-map.svg)\n\n另外，這裡出現了「框架」這個術語，Vue.js 將自己推廣為「漸進式框架」．關於這一點，我認為最好直接參考文檔的以下部分：\n\nhttps://vuejs.org/guide/introduction.html#the-progressive-framework\n\n## 官方文檔和本書的區別\n\n官方文檔專注於「如何使用 Vue.js」，提供了大量的教程和指南．\n\n然而，這本書採用了稍微不同的方法，專注於「Vue.js 是如何實現的」．我們將編寫實際代碼來創建一個迷你版本的 Vue.js．\n\n另外，這本書不是官方出版物，可能不夠詳盡．可能會有一些錯誤或遺漏，所以我很感謝任何反饋或更正．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/00-introduction/030-vue-core-components.md",
    "content": "# 構成 Vue.js 的關鍵要素\n\n## Vue.js 倉庫\n\nVue.js 可以在這個倉庫中找到：  \nhttps://github.com/vuejs/core\n\n有趣的是，這是 v3 的倉庫．對於 v2 和更早的版本，您可以在另一個倉庫中找到：  \nhttps://github.com/vuejs/vue\n\n為了本次討論，我們將專注於核心倉庫（v3）．\n\n## 構成 Vue.js 的主要要素\n\n讓我們首先對 Vue.js 的實現有一個整體的理解．\\\nVue.js 倉庫中有一個關於貢獻的 markdown 文件；  \n如果您感興趣，可以查看它以了解其架構．（不過，跳過也沒關係．）\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md\n\n從大的方面來說，Vue.js 包含以下主要組件：\n\n## 運行時（Runtime）\n\n運行時包含影響實際操作的所有內容 - 從渲染到組件狀態管理．\\\n這指的是在瀏覽器或伺服器（在 SSR 的情況下）上運行的用 Vue.js 開發的 Web 應用程式的全部內容．具體包括：\n\n### 組件系統\n\nVue.js 是一個面向組件的框架．根據用戶的需求，您可以可維護地創建和封裝組件以供重用．\\\n它還提供組件之間狀態共享（props/emits 或 provide/inject）和生命週期鉤子等功能．\n\n### 響應式系統\n\n它跟蹤組件持有的狀態，並在發生變化時更新螢幕．\\\n這種監控和響應機制稱為響應式．\n\n```ts\nimport { ref } from 'vue'\n\nconst count = ref(0)\n\n// 當執行這個函數時，顯示計數的螢幕也會更新\nconst increment = () => {\n  count.value++\n}\n```\n\n（僅僅通過改變一個值就能更新螢幕，這很神奇，對吧？）\n\n### 虛擬 DOM 系統\n\n虛擬 DOM 系統是 Vue.js 的另一個強大機制．它定義了一個模仿 DOM 的 JavaScript 對象在 JS 運行時中．\\\n更新時，它將當前的虛擬 DOM 與新的虛擬 DOM 進行比較，並僅將差異反映到真實 DOM 中．\\\n我們將在專門的章節中深入探討這一點．\n\n## 編譯器（Compiler）\n\n編譯器負責將開發者介面轉換為內部實現．\\\n「開發者介面」是指「使用 Vue.js 進行 Web 應用程式開發的開發者」和「Vue 內部操作」之間的邊界．\\\n本質上，當您使用 Vue.js 編寫時，有些部分顯然不是純 JavaScript - 比如模板指令或單文件組件．\\\nVue.js 提供這些語法並將它們轉換為純 JavaScript．\\\n此功能僅在開發階段使用，不是實際運行的 Web 應用程式的一部分．\\\n（它僅僅編譯為 JavaScript 代碼）．\n\n編譯器有兩個主要部分：\n\n### 模板編譯器\n\n顧名思義，這是模板部分的編譯器．\\\n具體來說，它處理 v-if 或 v-on 等指令，用戶組件標記（如 <Counter />）以及插槽等功能．\n\n### SFC 編譯器\n\n正如您可能猜到的，這代表單文件組件編譯器．\\\n它允許您在單個 .vue 文件中定義組件的模板，腳本和樣式．\\\n在 script setup 中使用的函數，如 [defineProps 或 defineEmits](https://cn.vuejs.org/api/sfc-script-setup#defineprops-defineemits) 也由此編譯器提供．\\\n這個 SFC 編譯器通常與 Webpack 或 Vite 等工具結合使用．\\\n作為其他工具插件的實現不在核心倉庫中．\\\nSFC 編譯器的主要功能在核心中，但插件在不同的倉庫中實現．\\\n（參考：[vitejs/vite-plugin-vue](https://github.com/vitejs/vite-plugin-vue)）\n\n順便說一下，我們將實現一個實際的 Vite 插件來操作我們的自定義 SFC 編譯器．\n\n## 窺探 vuejs/core 目錄\n\n現在我們對 Vue 的主要要素有了大致的了解，讓我們看看實際的源代碼是什麼樣子的（儘管我們只是在討論目錄）．\\\n主要源代碼存儲在「packages」目錄中．\n\nhttps://github.com/vuejs/core/tree/main/packages\n\n一些需要關注的關鍵目錄是：\n\n- compiler-core\n- compiler-dom\n- compiler-sfc\n- reactivity\n- runtime-core\n- runtime-dom\n- vue\n\n為了理解它們的相互依賴關係，貢獻指南中的圖表特別有見地．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n<br/>\n在這本書中，我們將為所有這些主題提供實現和解釋。\n"
  },
  {
    "path": "book/online-book/src/zh-tw/00-introduction/040-setup-project.md",
    "content": "# 如何進行本書學習和環境設置\n\n## Web Playground\n\n本書提供了一個 **Web Playground**，您可以直接在瀏覽器中試用每個章節的實現代碼．\n無需任何環境設置，您可以立即編輯和運行代碼，所以請先在這裡體驗 chibivue 的實際運行！\n\n### 如何啟動 Playground\n\n```sh\n$ git clone https://github.com/chibivue-land/chibivue\n$ cd chibivue\n$ pnpm install\n$ pnpm play\n```\n\n在瀏覽器中訪問顯示的 URL（例如：`http://localhost:5173/`）即可啟動 Playground．\n\n### Playground 佈局\n\n![Initial Web Playground screen](/figures/00-introduction/setup-project/web-playground-initial.png)\n\nPlayground 由四個區域組成：\n\n| 區域 | 說明 |\n|------|------|\n| **Explorer（左側）** | 顯示項目文件樹．點擊文件可在編輯器中打開 |\n| **Editor（中央）** | 使用 Monaco Editor 編輯代碼 |\n| **Preview（右側）** | 顯示在 WebContainer 上運行的開發伺服器預覽 |\n| **Terminal / Console（底部）** | 查看終端輸出和 console.log 內容 |\n\n### 使用方法\n\n1. **選擇章節**\n   從螢幕頂部的下拉選單中選擇您想學習的章節．\n   您也可以使用搜尋框過濾章節名稱．\n\n2. **點擊 Run**\n   點擊「Run」按鈕啟動 WebContainer，安裝依賴並啟動開發伺服器．\n   首次運行需要一些時間，稍等片刻後，結果將顯示在 Preview 區域．\n\n3. **編輯代碼**\n   在編輯器中編輯代碼，然後點擊「Apply」按鈕應用更改．\n   通過 HMR（熱模組替換），更改會即時反映．\n\n4. **查看控制台**\n   點擊「Console」標籤頁查看 console.log 等輸出內容．\n\n![Web Playground console output](/figures/00-introduction/setup-project/web-playground-console.png)\n\n::: tip\nWeb Playground 使用 [WebContainer](https://webcontainers.io/)．\n在某些瀏覽器或環境中可能無法運行．在這種情況下，請參考下面的本地環境設置．\n:::\n\n## 如何進行本書學習\n\n我們將立即開始 Vue.js 的簡單實現．以下是一些需要記住的要點，注意事項和其他重要資訊：\n\n- 項目名稱將是「chibivue」．我們將把本書中涵蓋的基本 Vue.js 實現稱為「chibivue」．\n- 如最初提到的，我們的主要方法將是「重複小型開發」．\n- 每個階段的源代碼都包含在本書的附錄中，可以在 https://github.com/chibivue-land/chibivue/tree/main/book/impls 找到．我們不會在書中為所有源代碼提供詳細解釋，所以請根據需要參考附錄．\n- 最終代碼依賴於幾個套件．DIY 內容的一個常見問題是關於「應該手工實現多少才能稱之為自製」的爭論．雖然我們不會在本書中手工編寫所有源代碼，但我們將積極使用與 Vue.js 官方代碼中使用的類似的套件．例如，我們將使用 [Babel](https://babeljs.io/)．請放心，我們的目標是使這本書盡可能對初學者友好，為必要的套件提供最少的解釋．\n\n## 環境設置\n\n現在，讓我們快速進入環境設置！\\\n我將列出我們將使用的工具和版本：\n\n- 運行時：[Node.js](https://nodejs.org/en) v24\n- 語言：[TypeScript](https://www.typescriptlang.org/)\n- 套件管理器：[pnpm](https://pnpm.io/) v10\n- 構建工具：[Vite](https://vite.dev/) v8\n\n## 安裝 Node.js\n\n你們大多數人可能都熟悉這一步．請自行設置．我們將跳過這裡的詳細解釋．\n\n## 安裝 pnpm\n\n你們中的許多人可能通常使用 npm 或 yarn．對於這本書，我們將使用 pnpm，所以請也安裝它．命令大多與 npm 相似．\nhttps://pnpm.io/installation\n\n\n## 創建項目\n\n::: details 急於開始的快速啟動...\n\n雖然我將解釋手動創建項目的步驟，但實際上有一個為設置準備的工具．  \n如果您覺得手動過程繁瑣，請隨時使用這個工具！\n\n1. 克隆 chibivue．\n\n   ```sh\n   $ git clone https://github.com/chibivue-land/chibivue\n   ```\n\n2. 執行腳本．  \n   輸入您想要設置的目錄路徑．\n\n   ```sh\n   $ cd chibivue\n   $ pnpm setup:book ../my-chibivue-project\n   ```\n\n:::\n\n在您選擇的任何目錄中創建項目．為了方便起見，我們將項目的根路徑表示為 `~`（例如，`~/src/main.ts`）．\n\n這次，我們將把主要的「chibivue」與測試其功能的遊樂場分開．遊樂場將簡單地調用「chibivue」並用 Vite 打包它．我們預期這樣的結構．\n\n```\n~\n|- examples\n|    |- playground\n|\n|- packages\n|- tsconfig.js\n```\n\n我們將在名為「examples」的目錄中實現遊樂場．\n我們將在「packages」中實現 chibivue 的核心 TypeScript 文件，並從示例端導入它們．\n\n以下是構建它的步驟．\n\n### 構建主項目\n\n```sh\n## 請創建一個專門用於 chibivue 的目錄並導航到其中。（此後將省略此類註釋。）\npwd # ~/\npnpm init\npnpm add -D @types/node\nmkdir packages\ntouch packages/index.ts\ntouch tsconfig.json\n```\n\ntsconfig.json 的內容\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"Bundler\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\npackages/index.ts 的內容\n\n```ts\nconsole.log(\"Hello, World\")\n```\n\n### 構建遊樂場端\n\n```sh\npwd # ~/\nmkdir examples\ncd examples\npnpm dlx create-vite\n\n## --------- 使用 Vite CLI 設置\n## Project name: playground\n## Select a framework: Vanilla\n## Select a variant: TypeScript\n```\n\n從用 Vite 創建的項目中刪除不必要的項目．\n\n```sh\npwd # ~/examples/playground\nrm -rf public\nrm -rf src # 我們將重新創建它，因為有不必要的文件。\nmkdir src\ntouch src/main.ts\n```\n\nsrc/main.ts 的內容\n\n※ 現在，「from」後面會有錯誤，但我們將在接下來的步驟中解決這個問題，所以沒有問題．\n\n```ts\nimport \"chibivue\"\n```\n\n按如下方式修改 index.html．\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n在 Vite 項目中配置別名，以便能夠導入您在 chibivue 中實現的內容．\n\n```sh\npwd # ~/examples/playground\ntouch vite.config.js\n```\n\nvite.config.js 的內容\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n})\n```\n\n按如下方式修改 tsconfig.json．\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Node\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n```\n\n最後，讓我們在 chibivue 項目的 package.json 中添加一個命令來啟動遊樂場並嘗試啟動它！\n\n將以下內容附加到 ~/package.json\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  }\n}\n```\n\n```sh\npwd # ~\npnpm dev\n```\n\n訪問使用此命令啟動的開發伺服器．如果顯示消息，則設置完成．\n\n![Hello chibivue rendered in the browser](/figures/00-introduction/setup-project/hello-chibivue-result.png)\n\n到此為止的源代碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls//00_introduction/010_project_setup)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/010-create-app-api.md",
    "content": "# 第一次渲染和 createApp API\n\n## 從哪裡開始？\n\n現在，讓我們開始逐步實現 chibivue．我們應該如何進行實現？\n\n這是作者在創建新東西時總是牢記的一點：首先，思考軟體將如何被使用．\\\n為了方便起見，讓我們稱之為「開發者介面」．\n\n這裡，「開發者」指的是使用 chibivue 開發 Web 應用程式的人，而不是 chibivue 本身的開發者．\\\n換句話說，在開發 chibivue 時，讓我們參考原始 Vue.js 的開發者介面作為參考．\\\n具體來說，讓我們看看在使用 Vue.js 開發 Web 應用程式時要寫什麼．\n\n## 開發者介面層級？\n\n我們在這裡需要注意的是，Vue.js 有多個開發者介面，每個介面都有不同的層級．這裡，層級指的是它與原始 JavaScript 的接近程度．\\\n例如，以下是使用 Vue 顯示 HTML 的開發者介面示例：\n\n1. 在單文件組件中編寫模板\n\n```vue\n<!-- App.vue -->\n<template>\n  <div>Hello world.</div>\n</template>\n```\n\n```ts\nimport { createApp } from 'vue'\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\n2. 使用 template 選項\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  template: '<div>Hello world.</div>',\n})\n\napp.mount('#app')\n```\n\n3. 使用 render 選項和 h 函數\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\n還有其他選項，但讓我們考慮這三個開發者介面．\\\n哪一個最接近原始 JavaScript？答案是「使用 render 選項和 h 函數」（選項 3）．\\\n選項 1 需要實現 SFC 編譯器和打包器（或載入器），選項 2 需要編譯傳遞給模板的 HTML（將其轉換為 JavaScript 代碼）才能工作．\n\n為了方便起見，讓我們稱更接近原始 JS 的開發者介面為「低級開發者介面」．\\\n這裡重要的是「從低級部分開始實現」．\\\n原因是在許多情況下，高級描述被轉換為低級描述並執行．\\\n換句話說，選項 1 和 2 最終都在內部轉換為選項 3 的形式．\\\n這種轉換的實現稱為「編譯器」．\n\n所以，讓我們從實現像選項 3 這樣的開發者介面開始！\n\n## createApp API 和渲染\n\n雖然我們的目標是選項 3 的形式，但我們仍然不太了解 h 函數，而且由於這本書的目標是增量開發，讓我們不要立即瞄準選項 3 的形式．\\\n相反，讓我們從實現一個返回要顯示的消息的簡單渲染函數開始．\n\n圖像 ↓\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n## 立即實現\n\n讓我們在 `~/packages/index.ts` 中創建 createApp 函數．\\\n注意：由於不需要輸出「Hello, World」，我們將刪除它．\n\n```ts\nexport type Options = {\n  render: () => string\n}\n\nexport type App = {\n  mount: (selector: string) => void\n}\n\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render()\n      }\n    },\n  }\n}\n```\n\n這非常簡單．讓我們在遊樂場中試試．\n\n`~/examples/playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n我們能夠在螢幕上顯示消息！做得好！\n\n![createApp example rendered in the browser](/figures/10-minimum-example/create-app-api/hello-create-app-result.png)\n\n到此為止的源代碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/010_create_app)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/015-package-architecture.md",
    "content": "# 套件架構\n\n## 重構\n\n您可能會想，「嗯？我們只實現了這麼一點，您就想重構？」但這本書的目標之一是「能夠閱讀 Vue.js 源代碼」．\n\n考慮到這一點，我希望始終關注 Vue.js 風格的文件和目錄結構．所以，請允許我做一點重構...\n\n### Vue.js 設計\n\n#### runtime-core 和 runtime-dom\n\n讓我稍微解釋一下官方 Vue.js 的結構．在這次重構中，我們將創建兩個目錄：「runtime-core」 和 「runtime-dom」．\n\n<KawaikoNote variant=\"question\" title=\"為什麼要分開？\">\n\n「程式碼已經能運行了，為什麼還要拆分？」你可能會這樣想．\\\n實際上 Vue.js 被設計成不僅可以在瀏覽器中運行，還可以在 SSR（伺服器端渲染）和原生應用（Vue Native）等環境中運行．\\\n這就是為什麼要將「純邏輯」和「DOM 操作」分離的原因！\n\n</KawaikoNote>\n\n為了解釋它們各自是什麼，「runtime-core」 包含 Vue.js 運行時的核心功能．在這個階段，可能很難理解什麼是核心，什麼不是．\n\n所以，我認為通過查看與 「runtime-dom」 的關係會更容易理解．顧名思義，「runtime-dom」 是一個包含依賴於 DOM 的實現的目錄．粗略地說，它可以理解為「依賴於瀏覽器的操作」．它包括 DOM 操作，如 querySelector 和 createElement．\n\n在 runtime-core 中，我們不編寫這樣的操作，而是設計它在純 TypeScript 的世界中描述 Vue.js 運行時的核心邏輯．例如，它包括與虛擬 DOM 和組件相關的實現．嗯，我認為隨著 chibivue 開發的進展，這會變得更清楚，所以如果您不理解，請暫時按照書中描述的進行重構．\n\n#### 每個文件的角色和依賴關係\n\n我們現在將在 runtime-core 和 runtime-dom 中創建一些文件．必要的文件如下：\n\n```sh\npwd # ~\nmkdir packages/runtime-core\nmkdir packages/runtime-dom\n\n## core\ntouch packages/runtime-core/index.ts\ntouch packages/runtime-core/apiCreateApp.ts\ntouch packages/runtime-core/component.ts\ntouch packages/runtime-core/componentOptions.ts\ntouch packages/runtime-core/renderer.ts\n\n## dom\ntouch packages/runtime-dom/index.ts\ntouch packages/runtime-dom/nodeOps.ts\n```\n\n至於這些文件的角色，僅僅用文字解釋可能很難理解，所以請參考以下圖表：\n\n![runtime-core and runtime-dom responsibilities](/figures/10-minimum-example/package-architecture/runtime-core-dom-overview.svg)\n\n#### 渲染器的設計\n\n如前所述，Vue.js 將依賴於 DOM 的部分與 Vue.js 的純核心功能分離．首先，我希望您注意 「runtime-core」 中的渲染器工廠和 「runtime-dom」 中的 nodeOps．在我們之前實現的示例中，我們直接在 createApp 返回的應用程式的 mount 方法中進行渲染．\n\n```ts\n// 這是之前的代碼\nexport const createApp = (options: Options): App => {\n  return {\n    mount: selector => {\n      const root = document.querySelector(selector)\n      if (root) {\n        root.innerHTML = options.render() // 渲染\n      }\n    },\n  }\n}\n```\n\n此時，代碼很短，一點也不複雜，所以乍一看似乎很好．然而，當我們將來為虛擬 DOM 編寫補丁渲染邏輯時，它會變得更加複雜．在 Vue.js 中，這個負責渲染的部分被分離為「渲染器」．那就是 「runtime-core/renderer.ts」．說到渲染，很容易想像它依賴於在 SPA 中控制瀏覽器 DOM 的 API（document）（創建元素，設置文本等）．因此，為了將這個依賴於 DOM 的部分與 Vue.js 的核心渲染邏輯分離，已經做了一些技巧．它是這樣工作的：\n\n- 在 `runtime-dom/nodeOps` 中實現一個用於 DOM 操作的對象．\n- 在 `runtime-core/renderer` 中實現一個工廠函數，該函數生成一個只包含渲染邏輯的對象．在這樣做時，確保將處理節點（不限於 DOM）的對象作為參數傳遞給工廠函數．\n- 在 `runtime-dom/index.ts` 中使用 `nodeOps` 和 `renderer` 的工廠來完成渲染器．\n\n這是圖表中用紅色突出顯示的部分．\n![Renderer dependency injection](/figures/10-minimum-example/package-architecture/renderer-dependency-injection.svg)\n\n讓我解釋一下源代碼．此時，虛擬 DOM 的渲染功能尚未實現，所以我們將創建與之前相同功能的代碼．\n\n首先，在 `runtime-core/renderer` 中實現用於節點（不限於 DOM）操作的對象介面．\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  setElementText(node: HostNode, text: string): void\n}\n\nexport interface RendererNode {\n  [key: string]: any\n}\n\nexport interface RendererElement extends RendererNode {}\n```\n\n目前，只有 `setElementText` 函數，但您可以想像將來會實現 `createElement` 和 `removeChild` 等函數．\n\n關於 `RendererNode` 和 `RendererElement`，請暫時忽略它們．（這裡的實現只是為成為節點的對象定義一個通用類型，而不依賴於 DOM．）  \n在此文件中實現渲染器工廠函數，該函數將 `RendererOptions` 作為參數．\n\n```ts\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  message: string,\n  container: HostElement,\n) => void\n\nexport function createRenderer(options: RendererOptions) {\n  const { setElementText: hostSetElementText } = options\n\n  const render: RootRenderFunction = (message, container) => {\n    hostSetElementText(container, message) // 在這種情況下，我們只是簡單地插入消息，所以實現是這樣的\n  }\n\n  return { render }\n}\n```\n\n接下來，在 `runtime-dom/nodeOps` 中實現 `nodeOps`．\n\n```ts\nimport { RendererOptions } from '../runtime-core'\n\nexport const nodeOps: RendererOptions<Node> = {\n  setElementText(node, text) {\n    node.textContent = text\n  },\n}\n```\n\n這裡沒有什麼特別困難的．\n\n現在，讓我們在 `runtime-dom/index.ts` 中完成渲染器．\n\n```ts\nimport { createRenderer } from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\n```\n\n這樣，渲染器的重構就完成了．\n\n#### DI 和 DIP\n\n讓我們看看渲染器的設計．總結一下：\n\n- 在 `runtime-core/renderer` 中實現一個工廠函數來生成渲染器．\n- 在 `runtime-dom/nodeOps` 中實現一個用於依賴於 DOM 的操作（操縱）的對象．\n- 在 `runtime-dom/index` 中結合工廠函數和 `nodeOps` 來生成渲染器．\n\n這些是「DIP」和「DI」的概念．\n\n<KawaikoNote variant=\"warning\" title=\"這有點難\">\n\nDI 和 DIP 是設計模式中比較難理解的概念．\\\n一開始只需要有個大概的印象就可以了！\\\n隨著你寫更多的程式碼，你會恍然大悟：「啊，原來是這樣！」\n\n</KawaikoNote>\n\n首先，讓我們談談 DIP（依賴倒置原則）．通過實現介面，我們可以倒置依賴關係．您應該注意的是在 `renderer.ts` 中實現的 `RendererOptions` 介面．工廠函數和 `nodeOps` 都應該遵守這個 `RendererOptions` 介面（依賴於 `RendererOptions` 介面）．\n\n<KawaikoNote variant=\"funny\" title=\"用做菜來比喻\">\n\n如果把 DIP 比作做菜的話...\\\n只要有「食譜（interface）」，無論食材是國產還是進口，都能做出同樣的菜．\\\n渲染器（廚師）只需要按照「RendererOptions（食譜）」來做，不需要關心實際的食材（DOM 操作或其他操作）．\n\n</KawaikoNote>\n\n通過使用這個，我們執行 DI．依賴注入（DI）是一種通過從外部注入對象所依賴的對象來減少依賴的技術．在這種情況下，渲染器依賴於實現 `RendererOptions` 的對象（在這種情況下是 `nodeOps`）．我們不是直接從渲染器實現這種依賴，而是將其作為工廠的參數接收．通過使用這些技術，我們確保渲染器不依賴於 DOM．\n\n<KawaikoNote variant=\"base\" title=\"總結一下\">\n\n**DIP**: 依賴介面（契約），而不是具體實現\\\n**DI**: 從外部接收依賴（注入它們）\n\n結合這兩者，可以讓程式碼更靈活，更易於測試！\n\n</KawaikoNote>\n\n如果您不熟悉 DI 和 DIP，它們可能是困難的概念，但它們是經常使用的重要技術，所以我希望您能夠自己研究和理解它們．\n\n### 完成 createApp\n\n現在，讓我們回到實現．現在渲染器已經生成，我們需要做的就是考慮以下圖表中的紅色區域．\n\n![createAppAPI factory flow](/figures/10-minimum-example/package-architecture/create-app-api-factory.svg)\n\n然而，這是一個簡單的任務．我們只需要實現 createApp 的工廠函數，以便它可以接受我們之前創建的渲染器．\n\n```ts\n// ~/packages/runtime-core apiCreateApp.ts\n\nimport { Component } from './component'\nimport { RootRenderFunction } from './renderer'\n\nexport interface App<HostElement = any> {\n  mount(rootContainer: HostElement | string): void\n}\n\nexport type CreateAppFunction<HostElement> = (\n  rootComponent: Component,\n) => App<HostElement>\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const message = rootComponent.render!()\n        render(message, rootContainer)\n      },\n    }\n\n    return app\n  }\n}\n```\n\n```ts\n// ~/packages/runtime-dom/index.ts\n\nimport {\n  CreateAppFunction,\n  createAppAPI,\n  createRenderer,\n} from '../runtime-core'\nimport { nodeOps } from './nodeOps'\n\nconst { render } = createRenderer(nodeOps)\nconst _createApp = createAppAPI(render)\n\nexport const createApp = ((...args) => {\n  const app = _createApp(...args)\n  const { mount } = app\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector)\n    if (!container) return\n    mount(container)\n  }\n\n  return app\n}) as CreateAppFunction<Element>\n```\n\n我將類型移動到了 `~/packages/runtime-core/component.ts`，但這並不重要，所以請參考源代碼（這只是與原始 Vue.js 對齊）．\n\n現在我們更接近原始 Vue.js 的源代碼，讓我們測試一下．如果消息仍然顯示，那就沒問題．\n\n到此為止的源代碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/015_package_architecture)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/020-simple-h-function.md",
    "content": "# 讓我們啟用 HTML 元素的渲染\n\n## 什麼是 h 函數？\n\n到目前為止，我們已經讓以下源代碼工作：\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({\n  render() {\n    return 'Hello world.'\n  },\n})\n\napp.mount('#app')\n```\n\n這是一個簡單地在螢幕上渲染「Hello World.」的函數．  \n由於只有一條消息有點孤單，讓我們考慮一個也可以渲染 HTML 元素的開發者介面．  \n這就是 `h 函數` 的用武之地．這個 `h` 代表 `hyperscript`，作為在 JavaScript 中編寫 HTML（超文本標記語言）的函數提供．\n\n> h() 是 hyperscript 的縮寫 - 意思是「產生 HTML（超文本標記語言）的 JavaScript」。這個名稱繼承自許多虛擬 DOM 實現共享的約定。一個更具描述性的名稱可能是 createVnode()，但當您必須在渲染函數中多次調用此函數時，較短的名稱會有所幫助。\n\n引用：https://vuejs.org/guide/extras/render-function.html#creating-vnodes\n\n讓我們看看 Vue.js 中的 h 函數．\n\n```ts\nimport { createApp, h } from 'vue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['HelloWorld']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n作為 h 函數的基本用法，您將標籤名稱指定為第一個參數，將屬性指定為第二個參數，將子元素陣列指定為第三個參數．  \n在這裡，我特別提到了「基本用法」，因為 h 函數實際上對其參數有多種語法，您可以省略第二個參數或不對子元素使用陣列．  \n但是，在這裡我們將以最基本的語法實現它．\n\n## 我們應該如何實現它？\n\n現在我們了解了開發者介面，讓我們決定如何實現它．  \n需要注意的重要一點是它如何用作渲染函數的返回值．  \n這意味著 `h` 函數返回某種對象並在內部使用該結果．\\\n由於複雜的子元素很難理解，讓我們考慮實現簡單 h 函數的結果．\n\n```ts\nconst result = h('div', { class: 'container' }, ['hello'])\n```\n\n`result` 中應該存儲什麼樣的結果？（我們應該如何格式化結果以及如何渲染它？）\n\n讓我們假設以下對象存儲在 `result` 中：\n\n```ts\nconst result = {\n  type: 'div',\n  props: { class: 'container' },\n  children: ['hello'],\n}\n```\n\n換句話說，我們將從渲染函數接收類似於上面的對象，並使用它來執行 DOM 操作並渲染它．\\\n圖像是這樣的（在 `createApp` 的 `mount` 內部）：\n\n```ts\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const node = rootComponent.render!()\n    render(node, rootContainer)\n  },\n}\n```\n\n嗯，唯一改變的是我們將 `message` 字串更改為 `node` 對象．  \n我們現在要做的就是在渲染函數中基於對象執行 DOM 操作．\n\n實際上，這個對象有一個名字，「虛擬 DOM」．  \n我們將在虛擬 DOM 章節中更多地解釋虛擬 DOM，所以現在只需記住這個名字．\\\n\n## 實現 h 函數\n\n首先，創建必要的文件．\n\n```sh\npwd # ~\ntouch packages/runtime-core/vnode.ts\ntouch packages/runtime-core/h.ts\n```\n\n在 vnode.ts 中定義類型．這就是我們在 vnode.ts 中要做的全部．\n\n```ts\nexport interface VNode {\n  type: string\n  props: VNodeProps\n  children: (VNode | string)[]\n}\n\nexport interface VNodeProps {\n  [key: string]: any\n}\n```\n\n接下來，在 h.ts 中實現函數體．\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return { type, props, children }\n}\n```\n\n現在，讓我們嘗試在遊樂場中使用 h 函數．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, ['Hello world.'])\n  },\n})\n\napp.mount('#app')\n```\n\n螢幕上的顯示被破壞了，但如果您在 apiCreateApp 中添加日誌，您可以看到它按預期工作．\n\n```ts\nmount(rootContainer: HostElement) {\n  const vnode = rootComponent.render!();\n  console.log(vnode); // 檢查日誌\n  render(vnode, rootContainer);\n},\n```\n\n現在，讓我們實現渲染函數．\\\n在 RendererOptions 中實現 `createElement`，`createText` 和 `insert`．\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode> {\n  createElement(type: string): HostNode // 添加\n\n  createText(text: string): HostNode // 添加\n\n  setElementText(node: HostNode, text: string): void\n\n  insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void // 添加\n}\n```\n\n在渲染函數中實現 `renderVNode` 函數．現在，我們忽略 `props`．\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  const {\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options\n\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === 'string') return hostCreateText(vnode)\n    const el = hostCreateElement(vnode.type)\n\n    for (const child of vnode.children) {\n      const childEl = renderVNode(child)\n      hostInsert(childEl, el)\n    }\n\n    return el\n  }\n\n  const render: RootRenderFunction = (vnode, container) => {\n    const el = renderVNode(vnode)\n    hostInsert(el, container)\n  }\n\n  return { render }\n}\n```\n\n在 runtime-dom 的 nodeOps 中，定義實際的 DOM 操作．\n\n```ts\nexport const nodeOps: RendererOptions<Node> = {\n  // 添加\n  createElement: tagName => {\n    return document.createElement(tagName)\n  },\n\n  // 添加\n  createText: (text: string) => {\n    return document.createTextNode(text)\n  },\n\n  setElementText(node, text) {\n    node.textContent = text\n  },\n\n  // 添加\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\n嗯，此時，您應該能夠在螢幕上渲染元素．\\\n嘗試在遊樂場中編寫和測試各種東西！\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h('button', {}, ['click me!']),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n耶！現在我們可以使用 h 函數來渲染各種標籤！\n\n![VNode log from a simple h function](/figures/10-minimum-example/simple-h-function/basic-vnode-log.png)\n\n到此為止的源代碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/025-event-handler-and-attrs.md",
    "content": "# 讓我們支援事件處理器和屬性\n\n## 僅僅顯示有點孤單\n\n既然有機會，讓我們實現 props，這樣我們就可以使用點擊事件和樣式．\n\n關於這部分，雖然直接在 renderVNode 中實現也可以，但讓我們嘗試在考慮遵循原始設計的同時進行．\n\n請注意原始 Vue.js 的 runtime-dom 目錄．\n\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src\n\n我希望您特別注意 `modules` 目錄和 `patchProp.ts` 文件．\n\n在 modules 目錄內，有用於操作類，樣式和其他 props 的文件．\nhttps://github.com/vuejs/core/tree/main/packages/runtime-dom/src/modules\n\n這些都在 patchProp.ts 中組合成一個名為 patchProp 的函數，並混合到 nodeOps 中．\n\n與其用文字解釋，我將嘗試基於這種設計來做．\n\n## 創建 patchProps 的框架\n\n首先，讓我們創建框架．\n\n```sh\npwd # ~\ntouch packages/runtime-dom/patchProp.ts\n```\n\n`runtime-dom/patchProp.ts` 的內容\n\n```ts\ntype DOMRendererOptions = RendererOptions<Node, Element>\n\nconst onRE = /^on[^a-z]/\nexport const isOn = (key: string) => onRE.test(key)\n\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    // patchEvent(el, key, value); // 我們稍後會實現這個\n  } else {\n    // patchAttr(el, key, value); // 我們稍後會實現這個\n  }\n}\n```\n\n由於 patchProp 的類型在 RendererOptions 中沒有定義，讓我們定義它．\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement\n> {\n  // 添加\n  patchProp(el: HostElement, key: string, value: any): void;\n  .\n  .\n  .\n```\n\n這樣，我們需要修改 nodeOps 以排除 patchProps 以外的部分．\n\n```ts\n// 省略 patchProp\nexport const nodeOps: Omit<RendererOptions, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n  .\n  .\n  .\n```\n\n然後，在 `runtime-dom/index` 中生成渲染器時，讓我們更改為一起傳遞 patchProp．\n\n```ts\nconst { render } = createRenderer({ ...nodeOps, patchProp })\n```\n\n## 事件處理器\n\n讓我們實現 patchEvent．\n\n```sh\npwd # ~\nmkdir packages/runtime-dom/modules\ntouch packages/runtime-dom/modules/events.ts\n```\n\n實現 events.ts．\n\n```ts\ninterface Invoker extends EventListener {\n  value: EventValue\n}\n\ntype EventValue = Function\n\nexport function addEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.addEventListener(event, handler)\n}\n\nexport function removeEventListener(\n  el: Element,\n  event: string,\n  handler: EventListener,\n) {\n  el.removeEventListener(event, handler)\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  value: EventValue | null,\n) {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {})\n  const existingInvoker = invokers[rawName]\n\n  if (value && existingInvoker) {\n    // patch\n    existingInvoker.value = value\n  } else {\n    const name = parseName(rawName)\n    if (value) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(value))\n      addEventListener(el, name, invoker)\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker)\n      invokers[rawName] = undefined\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase()\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event) => {\n    invoker.value(e)\n  }\n  invoker.value = initialValue\n  return invoker\n}\n```\n\n這有點長，但如果您拆分它，這是一個非常簡單的代碼．\n\naddEventListener 顧名思義，只是一個用於註冊事件監聽器的函數．\\\n雖然實際上需要在適當的時機刪除它，但我們現在將忽略它．\n\n在 patchEvent 中，我們用一個名為 invoker 的函數包裝監聽器並註冊監聽器．\\\n關於 parseName，它只是通過刪除「on」將 prop 鍵名（如 `onClick` 和 `onInput`）轉換為小寫（例如 click，input）．\n需要注意的一點是，為了不向同一元素添加重複的 addEventListeners，我們將 invoker 添加到名為 `_vei`（vue event invokers）的元素中．\\\n通過在補丁時更新 existingInvoker.value，我們可以在不添加重複 addEventListeners 的情況下更新處理器．\\\n術語「invoker」簡單地意味著「執行者」．沒有更深的含義；它只是一個存儲將實際執行的處理器的對象．\n\n現在讓我們將其合併到 patchProps 中，並嘗試在 renderVNode 中使用它．\n\npatchProps\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (el, key, value) => {\n  if (isOn(key)) {\n    patchEvent(el, key, value)\n  } else {\n    // patchAttr(el, key, value); // 我們稍後會實現這個\n  }\n}\n```\n\nruntime-core/renderer.ts 中的 renderVNode\n\n```ts\n  const {\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    insert: hostInsert,\n  } = options;\n  .\n  .\n  .\n  function renderVNode(vnode: VNode | string) {\n    if (typeof vnode === \"string\") return hostCreateText(vnode);\n    const el = hostCreateElement(vnode.type);\n\n    // 這裡\n    Object.entries(vnode.props).forEach(([key, value]) => {\n      hostPatchProp(el, key, value);\n    });\n    .\n    .\n    .\n```\n\n現在讓我們在遊樂場中運行它．我將嘗試顯示一個簡單的警報．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', {}, [\n      h('p', {}, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n我們現在可以使用 h 函數註冊事件處理器！\n\n![Event handler example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/event-handler-result.png)\n\n## 嘗試支援其他 props\n\n在此之後，只需對 setAttribute 做同樣的事情．\\\n我們將在 `modules/attrs.ts` 中實現這個．\\\n我希望您自己嘗試．答案將在本章末尾的源代碼中附上，所以請在那裡檢查．\\\n一旦您可以使這段代碼工作，您就達到了目標．\n\n```ts\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  render() {\n    return h('div', { id: 'my-app' }, [\n      h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n      h(\n        'button',\n        {\n          onClick() {\n            alert('Hello world!')\n          },\n        },\n        ['click me!'],\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n![Attribute patching example rendered in the browser](/figures/10-minimum-example/event-handler-and-attrs/attrs-result.png)\n\n現在我們可以處理廣泛的 HTML！\n\n到此為止的源代碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/020_simple_h_function)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/030-prerequisite-knowledge-for-the-reactivity-system.md",
    "content": "# 響應式系統的先決知識\n\n## 這次我們的目標開發者介面\n\n從這裡開始，我們將討論 Vue.js 的精髓，即響應式系統．\n\n<KawaikoNote variant=\"surprise\" title=\"重頭戲來了！\">\n\n這是 Vue.js 的核心！\\\n一旦理解了響應式系統，你就會明白 Vue.js 的「魔法」是如何實現的．\\\n雖然有點難，但讓我們一起努力吧！\n\n</KawaikoNote>\n\n之前的實現雖然看起來類似於 Vue.js，但在功能上實際上並不是 Vue.js．  \n我只是實現了初始的開發者介面，並使其能夠顯示各種 HTML．\n\n然而，就目前而言，一旦螢幕被渲染，它就保持不變，作為一個 Web 應用程式，它變成了一個靜態站點．  \n從現在開始，我們將添加狀態來創建更豐富的 UI，並在狀態改變時更新渲染．\n\n首先，讓我們像往常一樣思考它將是什麼樣的開發者介面．  \n這樣如何？\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n\n    const increment = () => {\n      state.count++\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n如果您習慣於使用單文件組件（SFC）進行開發，這可能看起來有點不熟悉．  \n這是一個使用 `setup` 選項來保存狀態並返回渲染函數的開發者介面．  \n實際上，Vue.js 有這樣的表示法．\n\nhttps://vuejs.org/api/composition-api-setup.html#usage-with-render-functions\n\n我們用 `reactive` 函數定義狀態，實現一個名為 `increment` 的函數來修改它，並將其綁定到按鈕的點擊事件．  \n總結我們想要做的事情：\n\n- 執行 `setup` 函數以從返回值獲取用於獲取 vnode 的函數\n- 使傳遞給 `reactive` 函數的對象變為響應式\n- 當按鈕被點擊時，狀態被更新\n- 跟蹤狀態更新，重新執行渲染函數，並重繪螢幕\n\n## 什麼是響應式系統？\n\n現在，讓我們回顧一下什麼是響應式．  \n讓我們參考官方文檔．\n\n> 響應式對象是 JavaScript 代理，其行為類似於普通對象。不同之處在於 Vue 可以跟蹤響應式對象上的屬性訪問和更改。\n\n[來源](https://vuejs.org/guide/essentials/reactivity-fundamentals)\n\n> Vue 最獨特的功能之一是其謙遜的響應式系統。組件的狀態由響應式 JavaScript 對象組成。當狀態改變時，視圖會更新。\n\n[來源](https://vuejs.org/guide/extras/reactivity-in-depth.html)\n\n總之，「響應式對象在有變化時更新螢幕」．  \n讓我們暫時擱置如何實現這一點，並實現前面提到的開發者介面．\n\n## setup 函數的實現\n\n我們需要做的非常簡單．  \n我們接收 `setup` 選項並執行它，然後我們可以像之前的 `render` 選項一樣使用它．\n\n編輯 `~/packages/runtime-core/componentOptions.ts`：\n\n```ts\nexport type ComponentOptions = {\n  render?: Function\n  setup?: () => Function // 添加\n}\n```\n\n然後使用它：\n\n```ts\n// createAppAPI\n\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    updateComponent()\n  },\n}\n```\n\n```ts\n// playground\n\nimport { createApp, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // 將來在這裡定義狀態\n    // const state = reactive({ count: 0 })\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', { style: 'color: red; font-weight: bold;' }, ['Hello world.']),\n        h(\n          'button',\n          {\n            onClick() {\n              alert('Hello world!')\n            },\n          },\n          ['click me!'],\n        ),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n嗯，就是這樣．  \n實際上，我們希望在狀態改變時執行這個 `updateComponent`．\n\n## 代理對象\n\n這是這次的主要主題．我想在狀態以某種方式改變時執行 `updateComponent`．\n\n關鍵是一個名為 Proxy 的對象．\n\n<KawaikoNote variant=\"question\" title=\"Proxy 是什麼？\">\n\nProxy 是 JavaScript 的標準功能，不是 Vue.js 發明的．\\\n可以理解為「監視和自定義對象訪問的機制」！\\\n通過它，我們可以檢測到「值被讀取」或「值被修改」．\n\n</KawaikoNote>\n\n首先，讓我解釋一下它們，而不是關於響應式系統的實現方法．\n\nhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy\n\nProxy 是一個非常有趣的對象．\n\n您可以通過將對象作為參數傳遞並像這樣使用 `new` 來使用它：\n\n```ts\nconst o = new Proxy({ value: 1 }, {})\nconsole.log(o.value) // 1\n```\n\n在這個例子中，`o` 的行為幾乎與普通對象相同．\n\n現在，有趣的是 Proxy 可以接受第二個參數並註冊一個處理器．\n這個處理器是對象操作的處理器．請看以下示例：\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n\n  {\n    get(target, key, receiver) {\n      console.log(`target:${target}, key: ${key}`)\n      return target[key]\n    },\n  },\n)\n```\n\n在這個例子中，我們正在為生成的對象編寫設置．\n具體來說，當訪問（get）此對象的屬性時，原始對象（target）和訪問的鍵名將輸出到控制台．\n讓我們在瀏覽器或其他地方檢查操作．\n\n![Proxy get trap console output](/figures/10-minimum-example/reactivity/proxy-get-trap.png)\n\n您可以看到為從此 Proxy 生成的對象的屬性讀取值而設置的 set 處理正在執行．\n\n同樣，您也可以為 set 設置它．\n\n```ts\nconst o = new Proxy(\n  { value: 1, value2: 2 },\n  {\n    set(target, key, value, receiver) {\n      console.log('hello from setter')\n      target[key] = value\n      return true\n    },\n  },\n)\n```\n\n![Proxy set trap console output](/figures/10-minimum-example/reactivity/proxy-set-trap.png)\n\n<KawaikoNote variant=\"funny\" title=\"這就是響應式的秘密！\">\n\n用 get 檢測「讀取」，用 set 檢測「寫入」...\\\n也就是說，在 set 的時機調用「更新螢幕的處理」，就能實現 **值變化時自動更新螢幕** 的魔法！\n\n</KawaikoNote>\n\n這就是理解 Proxy 的程度．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/035-try-implementing-a-minimum-reactivity-system.md",
    "content": "# 嘗試實現最小響應式系統\n\n## 使用 Proxy 的響應式機制\n\n::: info 與當前 `vuejs/core` 設計的差異\n截至 2024 年 12 月，Vue.js 的響應式系統採用基於雙向鏈表的觀察者模式．\\\n這個實現在 [Refactor reactivity system to use version counting and doubly-linked list tracking](https://github.com/vuejs/core/pull/10397) 中引入，對性能改進做出了重大貢獻．  \n\n然而，對於第一次實現響應式系統的人來說，這可能有些難以理解．在本章中，我們將創建傳統（優化前）系統的簡化實現．\\\n有關更接近當前實現的系統的更詳細解釋，請參考 [響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)．\n\n另一個重大改進 [feat(reactivity): more efficient reactivity system](https://github.com/vuejs/core/pull/5912) 將在單獨的章節中介紹．  \n:::\n\n為了再次明確目的，這次的目的是「在狀態改變時執行 `updateComponent`」．讓我使用 Proxy 解釋實現過程．\n\n首先，Vue.js 的響應式系統涉及 `target`，`Proxy`，`ReactiveEffect`，`Dep`，`track`，`trigger`，`targetMap` 和 `activeEffect`（目前是 `activeSub`）．\n\n<KawaikoNote variant=\"warning\" title=\"角色很多！\">\n\n突然出現了很多術語，但不要慌！\\\n如果我們一個一個地看每個角色，拼圖的碎片就會拼在一起．\\\n首先，讓我們目標是「大致把握全貌」．\n\n</KawaikoNote>\n\n首先，讓我們談談 targetMap 的結構．\ntargetMap 是某個目標的鍵和 deps 的映射．\nTarget 指的是您想要使其響應式的對象，dep 指的是您想要執行的效果（函數）．您可以這樣想．\n在代碼中，它看起來像這樣：\n\n```ts\ntype Target = any // 任何目標\ntype TargetKey = any // 目標擁有的任何鍵\n\nconst targetMap = new WeakMap<Target, KeyToDepMap>() // 在此模組中定義為全局變數\n\ntype KeyToDepMap = Map<TargetKey, Dep> // 目標的鍵和效果的映射\n\ntype Dep = Set<ReactiveEffect> // dep 有多個 ReactiveEffects\n\nclass ReactiveEffect {\n  constructor(\n    // 這裡，您給出想要實際應用為效果的函數（在這種情況下是 updateComponent）\n    public fn: () => T,\n  ) {}\n}\n```\n\n這意味著為「某個目標（對象）」的「某個鍵」註冊「某個效果」．\n\n僅僅看代碼可能很難理解，所以這裡有一個具體的例子和補充圖表．\\\n考慮如下組件：\n\n```ts\nexport default defineComponent({\n  setup() {\n    const state1 = reactive({ name: \"John\", age: 20 })\n    const state2 = reactive({ count: 0 })\n\n    function onCountUpdated() {\n      console.log(\"count updated\")\n    }\n\n    watch(() => state2.count, onCountUpdated)\n\n    return () => h(\"p\", {}, `name: ${state1.name}`)\n  }\n})\n```\n\n雖然我們在本章中還沒有實現 `watch`，但為了說明而寫在這裡．\\\n在這個組件中，targetMap 最終將形成如下：\n\n![targetMap structure](/figures/10-minimum-example/reactivity/target-map-structure.svg)\n\ntargetMap 的鍵是「某個目標」．在這個例子中，state1 和 state2 對應於此．\\\n這些目標擁有的鍵成為 targetMap 的鍵．\n與它們關聯的效果成為值．\n\n在部分 `() => h(\"p\", {}, name: ${state1.name})` 中，映射 `state1->name->updateComponentFn` 被註冊，在部分 `watch(() => state2.count, onCountUpdated)` 中，映射 `state2->count->onCountUpdated` 被註冊．\n\n這個基本結構負責其餘部分，然後我們考慮如何創建（註冊）targetMap 以及如何執行效果．\n\n<KawaikoNote variant=\"funny\" title=\"簡單地想\">\n\n**targetMap** 是一個記錄「誰影響誰」的筆記本．\\\n當 `state1.name` 改變時 → 運行 `updateComponent`\\\n當 `state2.count` 改變時 → 運行 `onCountUpdated`\\\n它記錄了這些關係！\n\n</KawaikoNote>\n\n這就是 `track` 和 `trigger` 概念的用武之地．\n顧名思義，`track` 是在 `targetMap` 中註冊的函數，`trigger` 是從 `targetMap` 檢索效果並執行它的函數．\n\n```ts\nexport function track(target: object, key: unknown) {\n  // ..\n}\n\nexport function trigger(target: object, key?: unknown) {\n  // ..\n}\n```\n\n這些 `track` 和 `trigger` 在 Proxy 的 get 和 set 處理器中實現．\n\n```ts\nconst state = new Proxy(\n  { count: 1 },\n  {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  },\n)\n```\n\n生成此 Proxy 的 API 是 reactive 函數．\n\n```ts\nfunction reactive<T>(target: T) {\n  return new Proxy(target, {\n    get(target, key, receiver) {\n      track(target, key)\n      return target[key]\n    },\n    set(target, key, value, receiver) {\n      target[key] = value\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n![reactive track and trigger flow](/figures/10-minimum-example/reactivity/reactive-track-trigger.svg)\n\n在這裡，您可能會注意到一個缺失的元素．那就是「在 track 中註冊哪個函數？」．\n答案是 `activeEffect` 的概念．\n這也像 targetMap 一樣在此模組中定義為全局變數，並在 ReactiveEffect 的 `run` 方法中設置．\n\n```ts\nlet activeEffect: ReactiveEffect | undefined\n\nclass ReactiveEffect {\n  constructor(\n    // 這裡，您給出想要實際應用為效果的函數（在這種情況下是 updateComponent）\n    public fn: () => T,\n  ) {}\n\n  run() {\n    activeEffect = this\n    return this.fn()\n  }\n}\n```\n\n要理解它是如何工作的，想像一個這樣的組件．\n\n```ts\n{\n  setup() {\n    const state = reactive({ count: 0 });\n    const increment = () => state.count++;\n\n    return function render() {\n      return h(\"div\", { id: \"my-app\" }, [\n        h(\"p\", {}, [`count: ${state.count}`]),\n        h(\n          \"button\",\n          {\n            onClick: increment,\n          },\n          [\"increment\"]\n        ),\n      ]);\n    };\n  },\n}\n```\n\n在內部，響應式是這樣形成的．\n\n```ts\n// chibivue 內部的實現\nconst app: App = {\n  mount(rootContainer: HostElement) {\n    const componentRender = rootComponent.setup!()\n\n    const updateComponent = () => {\n      const vnode = componentRender()\n      render(vnode, rootContainer)\n    }\n\n    const effect = new ReactiveEffect(updateComponent)\n    effect.run()\n  },\n}\n```\n\n逐步解釋，首先執行 `setup` 函數．\\\n此時生成響應式代理．換句話說，在此處創建的代理上執行的任何操作都將按照代理中配置的方式運行．\n\n```ts\nconst state = reactive({ count: 0 }) // 生成代理\n```\n\n接下來，我們傳遞 `updateComponent` 來創建 `ReactiveEffect`（觀察者端）．\n\n```ts\nconst effect = new ReactiveEffect(updateComponent)\n```\n\n在 `updateComponent` 中使用的 `componentRender` 是 `setup` 的 `返回值` 的函數，這個函數引用由代理創建的對象．\n\n```ts\nfunction render() {\n  return h('div', { id: 'my-app' }, [\n    h('p', {}, [`count: ${state.count}`]), // 引用由代理創建的對象\n    h(\n      'button',\n      {\n        onClick: increment,\n      },\n      ['increment'],\n    ),\n  ])\n}\n```\n\n當這個函數實際執行時，`state.count` 的 `getter` 函數被執行，`track` 被觸發．\n在這種情況下，讓我們執行效果．\n\n```ts\neffect.run()\n```\n\n然後，`updateComponent`（帶有 `updateComponent` 的 ReactiveEffect）被設置為 `activeEffect`．\n當在此狀態下觸發 `track` 時，`state.count` 和 `updateComponent`（帶有 `updateComponent` 的 ReactiveEffect）的映射在 `targetMap` 中註冊．\n這就是響應式的形成方式．\n\n現在，讓我們考慮執行 `increment` 時會發生什麼．\n由於 `increment` 正在重寫 `state.count`，`setter` 被執行，`trigger` 被觸發．\n`trigger` 基於 `state` 和 `count` 從 `targetMap` 中找到並執行 `effect`（在這種情況下是 updateComponent）．\n這就是螢幕更新的觸發方式！\n\n這使我們能夠實現響應式．\n\n這有點複雜，所以讓我們用圖表總結一下．\n\n![Reactivity setup flow during mount](/figures/10-minimum-example/reactivity/reactivity-setup-flow.svg)\n\n## 基於這些，讓我們實現它．\n\n最困難的部分是理解到這一點的一切，所以一旦您理解了，您所要做的就是編寫源代碼．\n然而，即使您只理解上述內容，可能有些人在不知道實際發生什麼的情況下無法理解．\n對於這些人，讓我們首先在這裡嘗試實現它．然後，在閱讀實際代碼時，請參考前面的部分！\n\n首先，讓我們創建必要的文件．我們將在 `packages/reactivity` 中創建它們．\n在這裡，我們將盡可能地意識到原始 Vue 的配置．\n\n```sh\npwd # ~\nmkdir packages/reactivity\n\ntouch packages/reactivity/index.ts\n\ntouch packages/reactivity/dep.ts\ntouch packages/reactivity/effect.ts\ntouch packages/reactivity/reactive.ts\ntouch packages/reactivity/baseHandler.ts\n```\n\n像往常一樣，`index.ts` 只是導出，所以我不會詳細解釋．在這裡導出您想要從 reactivity 外部套件使用的內容．\n\n接下來是 `dep.ts`．\n\n```ts\nimport { type ReactiveEffect } from './effect'\n\nexport type Dep = Set<ReactiveEffect>\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects)\n  return dep\n}\n```\n\n還沒有 `effect` 的定義，但我們稍後會實現它，所以沒關係．\n\n接下來是 `effect.ts`．\n\n```ts\nimport { Dep, createDep } from './dep'\n\ntype KeyToDepMap = Map<any, Dep>\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nexport let activeEffect: ReactiveEffect | undefined\n\nexport class ReactiveEffect<T = any> {\n  constructor(public fn: () => T) {}\n\n  run() {\n    // ※ 在執行 fn 之前保存 activeEffect，執行後恢復它。\n    // 如果您不這樣做，它將一個接一個地被覆蓋並表現出意外行為。（完成後讓我們將其恢復到原始狀態）\n    let parent: ReactiveEffect | undefined = activeEffect\n    activeEffect = this\n    const res = this.fn()\n    activeEffect = parent\n    return res\n  }\n}\n\nexport function track(target: object, key: unknown) {\n  let depsMap = targetMap.get(target)\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()))\n  }\n\n  let dep = depsMap.get(key)\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()))\n  }\n\n  if (activeEffect) {\n    dep.add(activeEffect)\n  }\n}\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  const dep = depsMap.get(key)\n\n  if (dep) {\n    const effects = [...dep]\n    for (const effect of effects) {\n      effect.run()\n    }\n  }\n}\n```\n\n到目前為止我還沒有解釋 `track` 和 `trigger` 的內容，但它們只是從 `targetMap` 註冊和檢索並執行它們，所以請嘗試仔細閱讀它們．\n\n接下來是 `baseHandler.ts`．在這裡，我們定義響應式代理的處理器．\n嗯，您可以直接在 `reactive` 中實現它，但我遵循了原始 Vue，因為它是這樣的．\n實際上，有各種代理，如 `readonly` 和 `shallow`，所以想法是在這裡實現這些代理的處理器．（雖然這次我們不會這樣做）\n\n```ts\nimport { track, trigger } from './effect'\nimport { reactive } from './reactive'\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get(target: object, key: string | symbol, receiver: object) {\n    track(target, key)\n\n    const res = Reflect.get(target, key, receiver)\n    // 如果它是一個對象，使其響應式（這也允許嵌套對象是響應式的）。\n    if (res !== null && typeof res === 'object') {\n      return reactive(res)\n    }\n\n    return res\n  },\n\n  set(target: object, key: string | symbol, value: unknown, receiver: object) {\n    let oldValue = (target as any)[key]\n    Reflect.set(target, key, value, receiver)\n    // 檢查值是否已更改\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key)\n    }\n    return true\n  },\n}\n\nconst hasChanged = (value: any, oldValue: any): boolean =>\n  !Object.is(value, oldValue)\n```\n\n在這裡，出現了 `Reflect`，它類似於 `Proxy`，但 `Proxy` 是為對象編寫元設置，而 `Reflect` 是對現有對象執行操作．\n`Proxy` 和 `Reflect` 都是 JS 引擎中與對象相關的元編程 API，它們允許您與正常使用對象相比執行元操作．\n您可以執行更改對象的函數，執行讀取對象的函數，檢查鍵是否存在，並執行各種元操作．\n現在，可以理解 `Proxy` 是在創建對象階段的元設置，`Reflect` 是對現有對象的元操作．\n\n接下來是 `reactive.ts`．\n\n```ts\nimport { mutableHandlers } from './baseHandler'\n\nexport function reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\n現在 `reactive` 的實現完成了，讓我們嘗試在掛載時使用它們．\n`~/packages/runtime-core/apiCreateApp.ts`．\n\n```ts\nimport { ReactiveEffect } from '../reactivity'\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const app: App = {\n      mount(rootContainer: HostElement) {\n        const componentRender = rootComponent.setup!()\n\n        const updateComponent = () => {\n          const vnode = componentRender()\n          render(vnode, rootContainer)\n        }\n\n        // 從這裡\n        const effect = new ReactiveEffect(updateComponent)\n        effect.run()\n        // 到這裡\n      },\n    }\n\n    return app\n  }\n}\n```\n\n現在，讓我們在遊樂場中嘗試它．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n哎呀...\n\n渲染現在工作正常，但似乎有些不對勁．\n嗯，這並不奇怪，因為在 `updateComponent` 中，我們每次都創建元素．\n所以，讓我們在每次渲染之前刪除所有元素．\n\n![Reactive example mistake in the browser](/figures/10-minimum-example/reactivity/reactive-example-mistake.png)\n\n像這樣修改 `~/packages/runtime-core/renderer.ts` 中的 `render` 函數：\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild) // 添加代碼以刪除所有元素\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\n現在，這樣如何？\n\n![Reactive example rendered in the browser](/figures/10-minimum-example/reactivity/reactive-example-result.png)\n\n現在似乎工作正常！\n\n現在我們可以使用 `reactive` 更新螢幕！\n\n<KawaikoNote variant=\"surprise\" title=\"恭喜！\">\n\n響應式系統的基礎已經完成！\\\n你理解了 Vue.js「值變化時自動更新螢幕」魔法背後的秘密了嗎？\\\n克服了這一關，你對 Vue.js 內部已經有了很深的理解！\n\n</KawaikoNote>\n\n到此為止的源代碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/030_reactive_system)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/040-minimum-virtual-dom.md",
    "content": "# 最小虛擬 DOM\n\n## 虛擬 DOM 用於什麼？\n\n通過在上一章中引入響應式系統，我們能夠動態更新螢幕．讓我們再次查看當前渲染函數的內容．\n\n```ts\nconst render: RootRenderFunction = (vnode, container) => {\n  while (container.firstChild) container.removeChild(container.firstChild)\n  const el = renderVNode(vnode)\n  hostInsert(el, container)\n}\n```\n\n有些人可能在上一章中注意到這個函數中有很多浪費．\n\n看看遊樂場．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return function render() {\n      return h('div', { id: 'my-app' }, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n    }\n  },\n})\n```\n\n問題是當執行 increment 時，只有 `count: ${state.count}` 部分發生變化，但在 renderVNode 中，所有 DOM 元素都被刪除並從頭重新創建．這感覺非常浪費．\\\n雖然現在看起來工作正常，因為它仍然很小，但您可以很容易地想像，如果您在開發 Web 應用程式時每次都必須從頭重新創建複雜的 DOM，性能將大大降低．\\\n因此，由於我們已經有了虛擬 DOM，我們希望實現一個比較當前虛擬 DOM 與之前虛擬 DOM 的實現，並僅使用 DOM 操作更新存在差異的部分．\\\n現在，這是本章的主要主題．\n\n讓我們看看我們想在源代碼中做什麼．當我們有像上面這樣的組件時，渲染函數的返回值變成如下的虛擬 DOM．在初始渲染時，計數為 0，所以它看起來像這樣：\n\n```ts\nconst vnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 0`]\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      [\"increment\"]\n    }\n  ]\n}\n```\n\n讓我們保留這個 vnode 並為下一次渲染準備另一個 vnode．以下是第一次點擊按鈕時的 vnode：\n\n```ts\nconst nextVnode = {\n  type: \"div\",\n  props: { id: \"my-app\" },\n  children: [\n    {\n      type: \"p\",\n      props: {},\n      children: [`count: 1`] // 只想更新這部分\n    },\n    {\n      type: \"button\",\n      { onClick: increment },\n      [\"increment\"]\n    }\n  ]\n}\n```\n\n現在，有了這兩個 vnodes，螢幕處於 vnode 的狀態（在它變成 nextVnode 之前）．\\\n我們希望將這兩個傳遞給一個名為 patch 的函數，並僅渲染差異．\n\n```ts\nconst vnode = {...}\nconst nextVnode = {...}\npatch(vnode, nextVnode, container)\n```\n\n我之前介紹了函數名，但這種差異渲染稱為「patch」．\\\n有時也稱為「reconciliation」．通過使用這兩個虛擬 DOM，您可以高效地更新螢幕．\n\n## 在實現 patch 函數之前\n\n這與主要主題沒有直接關係，但讓我們在這裡做一個輕微的重構（因為這對我們接下來要討論的內容很方便）．\\\n讓我們在 vnode.ts 中創建一個名為 createVNode 的函數，並讓 h 函數調用它．\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: unknown,\n): VNode {\n  const vnode: VNode = { type, props, children: [] }\n  return vnode\n}\n```\n\n也更改 h 函數．\n\n```ts\nexport function h(\n  type: string,\n  props: VNodeProps,\n  children: (VNode | string)[],\n) {\n  return createVNode(type, props, children)\n}\n```\n\n現在，讓我們進入正題．到目前為止，VNode 擁有的小元素的類型一直是 `(Vnode | string)[]`，但僅將 Text 視為字串是不夠的，所以讓我們嘗試將其統一為 VNode．\\\nText 不僅僅是一個字串，它作為 HTML TextElement 存在，所以它包含的資訊比僅僅一個字串更多．\\\n我們希望將其視為 VNode 以便處理周圍的資訊．\\\n具體來說，讓我們使用符號 Text 將其作為 VNode 的類型．\\\n例如，當有像 `\"hello\"` 這樣的文本時，\n\n```ts\n{\n  type: Text,\n  props: null,\n  children: \"hello\"\n}\n```\n\n是表示形式．\n\n另外，這裡需要注意的一點是，當執行 h 函數時，我們將繼續使用傳統的表達式，我們將通過在渲染函數中應用名為 normalize 的函數來轉換它，以表示如上所述的 Text．這樣做是為了匹配原始的 Vue.js．\n\n`~/packages/runtime-core/vnode.ts`;\n\n```ts\nexport const Text = Symbol();\n\nexport type VNodeTypes = string | typeof Text;\n\nexport interface VNode<HostNode = any> {\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  children: VNodeNormalizedChildren;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\n// 規範化後的類型\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\ntype VNodeChildAtom = VNode | string;\n\nexport function createVNode(..){..} // 省略\n\n// 實現 normalize 函數（在 renderer.ts 中使用）\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return { ...child } as VNode;\n  } else {\n    // 將字串轉換為前面介紹的所需形式\n    return createVNode(Text, null, String(child));\n  }\n}\n```\n\n現在 Text 可以被視為 VNode．\n\n## patch 函數的設計\n\n首先，讓我們看看代碼庫中 patch 函數的設計．\\\n（我們不需要在這裡實現它，只需理解它．）\\\npatch 函數比較兩個 vnodes，vnode1 和 vnode2．但是，vnode1 最初不存在．\\\n因此，patch 函數分為兩個過程：「初始（從 vnode2 生成 dom）」和「更新 vnode1 和 vnode2 之間的差異」．\\\n這些過程分別命名為「mount」和「patch」．\\\n它們分別對 ElementNode 和 TextNode 執行（結合為「process」，每個都有「mount」和「patch」名稱）．\n\n<img   \n    src=\"/figures/10-minimum-example/virtual-dom/patch-function-architecture.svg\"\n    alt=\"Patch Function Architecture\"   \n    style=\"background-color: white;\"\n/>\n\n```ts\nconst patch = (\n  n1: VNode | string | null,\n  n2: VNode | string,\n  container: HostElement,\n) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else {\n    processElement(n1, n2, container)\n  }\n}\n\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: HostElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    patchElement(n1, n2)\n  }\n}\n\nconst processText = (n1: string | null, n2: string, container: HostElement) => {\n  if (n1 == null) {\n    mountText(n2, container)\n  } else {\n    patchText(n1, n2)\n  }\n}\n```\n\n## 實際實現\n\n現在讓我們實際實現虛擬 DOM 的 patch 函數．\\\n首先，我們希望在 vnode 掛載時在 vnode 中有對實際 DOM 的引用，無論它是 Element 還是 Text．\\\n所以我們向 vnode 添加「el」屬性．\n\n`~/packages/runtime-core/vnode.ts`\n\n```ts\nexport interface VNode<HostNode = RendererNode> {\n  type: VNodeTypes\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined // [!code ++]\n}\n```\n\n現在讓我們轉到 `~/packages/runtime-core/renderer.ts`．\\\n我們將在 `createRenderer` 函數內部實現它並刪除 `renderVNode` 函數．\n\n```ts\nexport function createRenderer(options: RendererOptions) {\n  // .\n  // .\n  // .\n\n  const patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n    const { type } = n2\n    if (type === Text) {\n      // processText(n1, n2, container);\n    } else {\n      // processElement(n1, n2, container);\n    }\n  }\n}\n```\n\n讓我們從 `processElement` 和 `mountElement` 開始實現．\n\n```ts\nconst processElement = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountElement(n2, container)\n  } else {\n    // patchElement(n1, n2);\n  }\n}\n\nconst mountElement = (vnode: VNode, container: RendererElement) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children, el) // TODO:\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\n由於它是一個元素，我們還需要掛載其子元素．\\\n讓我們使用我們之前創建的 `normalize` 函數．\n\n```ts\nconst mountChildren = (children: VNode[], container: RendererElement) => {\n  for (let i = 0; i < children.length; i++) {\n    const child = (children[i] = normalizeVNode(children[i]))\n    patch(null, child, container)\n  }\n}\n```\n\n這樣，我們已經實現了元素的掛載．\\\n接下來，讓我們轉到掛載 Text．\\\n但是，這只是一個簡單的 DOM 操作．\\\n在設計說明中，我們將其分為 `mountText` 和 `patchText` 函數，但由於處理不多，並且預計將來不會變得更複雜，讓我們直接編寫它．\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // TODO: patch\n  }\n}\n```\n\n現在，隨著初始渲染的掛載完成，讓我們將一些處理從 `createAppAPI` 中的 `mount` 函數移動到 `render` 函數，以便我們可以保存兩個 vnodes．\\\n具體來說，我們將 `rootComponent` 傳遞給 `render` 函數並在其中執行 ReactiveEffect 註冊．\n\n```ts\nreturn function createApp(rootComponent) {\n  const app: App = {\n    mount(rootContainer: HostElement) {\n      // 只傳遞 rootComponent\n      render(rootComponent, rootContainer)\n    },\n  }\n}\n```\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\n現在，讓我們嘗試在遊樂場中渲染，看看它是否工作！\n\n由於我們還沒有實現 patch 函數，螢幕不會更新．\n\n所以，讓我們繼續編寫 patch 函數．\n\n```ts\nconst patchElement = (n1: VNode, n2: VNode) => {\n  const el = (n2.el = n1.el!)\n\n  const props = n2.props\n\n  patchChildren(n1, n2, el)\n\n  for (const key in props) {\n    if (props[key] !== n1.props?.[key]) {\n      hostPatchProp(el, key, props[key])\n    }\n  }\n}\n\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\nText 節點也是如此．\n\n```ts\nconst processText = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    hostInsert((n2.el = hostCreateText(n2.children as string)), container)\n  } else {\n    // 添加 patch 邏輯\n    const el = (n2.el = n1.el!)\n    if (n2.children !== n1.children) {\n      hostSetText(el, n2.children as string)\n    }\n  }\n}\n```\n\n※ 關於 patchChildren，通常我們需要通過添加 key 屬性來處理動態長度的子元素，但由於我們正在實現一個小的虛擬 DOM，我們不會在這裡涵蓋其實用性．\\\n如果您感興趣，請參考基礎虛擬 DOM 部分．\\\n在這裡，我們的目標是在一定程度上理解虛擬 DOM 的實現和作用．\n\n現在我們可以執行差異渲染，讓我們看看遊樂場．\n\n![patch rendering result in the browser](/figures/10-minimum-example/virtual-dom/patch-rendering-result.png)\n\n我們已經成功使用虛擬 DOM 實現了補丁！！！！！恭喜！\n\n到此為止的源代碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/040_vdom_system)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/050-minimum-component.md",
    "content": "# 我想使用基於組件的方法進行開發\n\n## 基於整理現有實現的思考\n\n到目前為止，我們已經小規模地實現了 createApp API，響應式系統和虛擬 DOM 系統．\\\n通過當前的實現，我們可以使用響應式系統動態更改 UI，並使用虛擬 DOM 系統執行高效渲染．\\\n然而，作為開發者介面，所有內容都寫在 createAppAPI 中．\\\n實際上，我想更多地分割檔案並實現通用組件以實現可重用性．\\\n首先，讓我們回顧一下現有實現中當前混亂的部分．請查看 renderer.ts 中的 render 函式．\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const componentRender = rootComponent.setup!()\n\n  let n1: VNode | null = null\n  let n2: VNode = null!\n\n  const updateComponent = () => {\n    const n2 = componentRender()\n    patch(n1, n2, container)\n    n1 = n2\n  }\n\n  const effect = new ReactiveEffect(updateComponent)\n  effect.run()\n}\n```\n\n在 render 函式中，直接定義了關於根組件的資訊．\\\n實際上，n1，n2，updateComponent 和 effect 對每個組件都存在．\\\n事實上，從現在開始，我想在使用者端定義組件（在某種意義上是建構函式）並實例化它．\\\n我希望實例具有 n1，n2 和 updateComponent 等屬性．\\\n因此，讓我們考慮將這些封裝為組件實例．\n\n讓我們在 `~/packages/runtime-core/component.ts` 中定義一個叫做 `ComponentInternalInstance` 的東西．\\\n這將是實例的類型．\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component // 原始使用者定義的組件（舊的 rootComponent（實際上不僅僅是根組件））\n  vnode: VNode // 稍後解釋\n  subTree: VNode // 舊的 n1\n  next: VNode | null // 舊的 n2\n  effect: ReactiveEffect // 舊的 effect\n  render: InternalRenderFunction // 舊的 componentRender\n  update: () => void // 舊的 updateComponent\n  isMounted: boolean\n}\n\nexport type InternalRenderFunction = {\n  (): VNodeChild\n}\n```\n\n這個實例擁有的 vnode，subTree 和 next 屬性有點複雜，但從現在開始，我們將實現它，以便可以將 ConcreteComponent 指定為 VNode 的類型．\\\n在 instance.vnode 中，我們將保留 VNode 本身．\\\n而 subTree 和 next 將保存該組件的渲染結果 VNode．（這與之前的 n1 和 n2 相同）\n\n在圖像方面，\n\n```ts\nconst MyComponent = {\n  setup() {\n    return h('p', {}, ['hello'])\n  },\n}\n\nconst App = {\n  setup() {\n    return h(MyComponent, {}, [])\n  },\n}\n```\n\n您可以像這樣使用它，如果讓實例成為 MyComponent 的實例，instance.vnode 將保存 `h(MyComponent, {}, [])` 的結果，instance.subTree 將保存 `h(\"p\", {}, [\"hello\"])` 的結果．\n\n現在，讓我們實現它，以便您可以將組件指定為 h 函式的第一個參數．\\\n但是，這只是接收定義組件的物件作為類型的問題．\\\n在 `~/packages/runtime-core/vnode.ts` 中\n\n```ts\nexport type VNodeTypes = string | typeof Text | object // 添加 object;\n```\n\n在 `~/packages/runtime-core/h.ts` 中\n\n```ts\nexport function h(\n  type: string | object, // 添加 object\n  props: VNodeProps\n) {..}\n```\n\n讓我們也確保 VNode 有一個組件實例．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  component: ComponentInternalInstance | null // 添加\n}\n```\n\n因此，渲染器也需要處理組件．\\\n實現類似於 `processElement` 和 `processText` 的 `processComponent` 來處理組件，並且還實現 `mountComponent` 和 `patchComponent`（或 `updateComponent`）．\n\n首先，讓我們從概述和詳細說明開始．\n\n![Component instance flow](/figures/10-minimum-example/minimum-component/component-instance-flow.svg)\n\n```ts\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    // 添加分支\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n) => {\n  if (n1 == null) {\n    mountComponent(n2, container)\n  } else {\n    updateComponent(n1, n2)\n  }\n}\n\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // TODO:\n}\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  // TODO:\n}\n```\n\n現在，讓我們看看 `mountComponent`．有三件事要做．\n\n1. 創建組件的實例．\n2. 執行 `setup` 函式並將結果儲存在實例中．\n3. 創建 `ReactiveEffect` 並將其儲存在實例中．\n\n首先，讓我們在 `component.ts` 中實現一個函式來創建組件的實例（類似於建構函式）．\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    type,\n    vnode,\n    next: null,\n    effect: null!,\n    subTree: null!,\n    update: null!,\n    render: null!,\n    isMounted: false,\n  }\n\n  return instance\n}\n```\n\n雖然每個屬性的類型都是非空的，但我們在創建實例時用 null 初始化它們（遵循原始 Vue.js 的設計）．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n  // TODO: setup component\n  // TODO: setup effect\n}\n```\n\n接下來是 `setup` 函式．\\\n我們需要將之前直接在 `render` 函式中編寫的程式碼移動到這裡，並將結果儲存在實例中而不是使用變數．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  // TODO: setup effect\n}\n```\n\n最後，讓我們將創建 effect 的程式碼合併到一個名為 `setupRenderEffect` 的函式中．\\\n同樣，主要任務是將之前直接在 `render` 函式中實現的程式碼移動到這裡，同時利用實例的狀態．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup() as InternalRenderFunction\n  }\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render } = instance\n\n    if (!instance.isMounted) {\n      // mount process\n      const subTree = (instance.subTree = normalizeVNode(render()))\n      patch(null, subTree, container)\n      initialVNode.el = subTree.el\n      instance.isMounted = true\n    } else {\n      // patch process\n      let { next, vnode } = instance\n\n      if (next) {\n        next.el = vnode.el\n        next.component = instance\n        instance.vnode = next\n        instance.next = null\n      } else {\n        next = vnode\n      }\n\n      const prevTree = instance.subTree\n      const nextTree = normalizeVNode(render())\n      instance.subTree = nextTree\n\n      patch(prevTree, nextTree, hostParentNode(prevTree.el!)!) // ※ 1\n      next.el = nextTree.el\n    }\n  }\n\n  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn))\n  const update = (instance.update = () => effect.run()) // 註冊到 instance.update\n  update()\n}\n```\n\n※ 1: 請在 `nodeOps` 中實現一個名為 `parentNode` 的函式，用於檢索父 Node．\n\n```ts\nparentNode: (node) => {\n    return node.parentNode;\n},\n```\n\n我認為這並不特別困難，儘管有點長．\\\n在 `setupRenderEffect` 函式中，更新函式被註冊為實例的 `update` 方法，所以在 `updateComponent` 中，我們只需要呼叫該函式．\n\n```ts\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!\n  instance.next = n2\n  instance.update()\n}\n```\n\n最後，由於到目前為止在 `render` 函式中定義的實現不再需要，我們將刪除它．\n\n```ts\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  patch(null, vnode, container)\n}\n```\n\n現在我們可以渲染組件了．讓我們嘗試創建一個 `playground` 組件作為示例．\\\n通過這種方式，我們可以將渲染分為組件．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst CounterComponent = {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n        h(CounterComponent, {}, []),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/051-component-props.md",
    "content": "# 組件 Props\n\n## 開發者介面\n\n讓我們從 props 開始．\\\n讓我們思考一下最終的開發者介面．\\\n讓我們考慮將 props 作為 `setup` 函式的第一個參數傳遞．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\n## 實現\n\n基於此，讓我們思考一下我們想在 `ComponentInternalInstance` 中擁有的資訊．\\\n我們需要指定為 `props: { message: { type: String } }` 的 props 定義，以及一個實際保存 props 值的屬性，所以我們添加以下內容：\n\n```ts\nexport type Data = Record<string, unknown>\n\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  propsOptions: Props // 保存像 `props: { message: { type: String } }` 這樣的物件\n\n  props: Data // 保存從父組件傳遞的實際資料（在這種情況下，它將是像 `{ message: \"hello\" }` 這樣的東西）\n}\n```\n\n創建一個名為 `~/packages/runtime-core/componentProps.ts` 的新檔案，內容如下：\n\n```ts\nexport type Props = Record<string, PropOptions | null>\n\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null\n  required?: boolean\n  default?: null | undefined | object\n}\n\nexport type PropType<T> = { new (...args: any[]): T & {} }\n```\n\n在實現組件時將其添加到選項中．\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any> // 添加\n  setup?: () => Function\n  render?: Function\n}\n```\n\n當使用 `createComponentInstance` 生成實例時，在生成實例時將 propsOptions 設定到實例中．\n\n```ts\nexport function createComponentInstance(\n  vnode: VNode\n): ComponentInternalInstance {\n  const type = vnode.type as Component;\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    propsOptions: type.props || {},\n    props: {},\n```\n\n讓我們思考如何形成 `instance.props`．\\\n在組件掛載時，根據 propsOptions 過濾 vnode 持有的 props．\\\n使用 `reactive` 函式將過濾後的物件轉換為響應式物件，並將其分配給 `instance.props`．\n\n在 `componentProps.ts` 中實現一個名為 `initProps` 的函式來執行這一系列步驟．\n\n```ts\nexport function initProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const props: Data = {}\n  setFullProps(instance, rawProps, props)\n  instance.props = reactive(props)\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      if (options && options.hasOwnProperty(key)) {\n        props[key] = value\n      }\n    }\n  }\n}\n```\n\n在掛載時實際執行 `initProps`，並將 props 作為參數傳遞給 `setup` 函式．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    // init props\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      instance.render = component.setup(\n        instance.props // 將 props 傳遞給 setup\n      ) as InternalRenderFunction;\n    }\n    // .\n    // .\n    // .\n}\n```\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (props: Record<string, any>) => Function // 接收 props\n  render?: Function\n}\n```\n\n此時，props 應該傳遞給子組件，所以讓我們在遊樂場中檢查它．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n      ])\n  },\n})\n```\n\n但是，這還不夠，因為當 props 更改時渲染不會更新．\n\n```ts\nconst MyComponent = {\n  props: { message: { type: String } },\n\n  setup(props: { message: string }) {\n    return () => h('div', { id: 'my-app' }, [`message: ${props.message}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { message: state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```\n\n要使此組件工作，我們需要在 `componentProps.ts` 中實現 `updateProps` 並在組件更新時執行它．\n\n`~/packages/runtime-core/componentProps.ts`\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  Object.assign(props, rawProps)\n}\n```\n\n讓我們整理一下組件更新處理的流程．\\\n當父組件重新渲染時，傳遞給子組件的 props 可能會改變．\\\n流程如下：\n\n1. 父組件的 `render` 函式被執行，為子組件生成新的 VNode\n2. 在 `patch` 處理中，`processComponent` 被調用，比較現有組件（`n1`）和新的 VNode（`n2`）\n3. 如果存在現有組件，則調用 `updateComponent` 函式\n\n首先，在 `ComponentInternalInstance` 中添加 `next` 屬性．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  vnode: VNode // 當前的VNode\n  next: VNode | null // 當有來自父組件的更新請求時，新的VNode會被設定在這裡\n  // .\n  // .\n}\n```\n\n接下來，在 `processComponent` 中實現已掛載組件的更新處理．\n\n```ts\nconst processComponent = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  if (n1 == null) {\n    mountComponent(n2, container);\n  } else {\n    updateComponent(n1, n2); // 添加\n  }\n};\n\nconst updateComponent = (n1: VNode, n2: VNode) => {\n  const instance = (n2.component = n1.component)!; // 將實例引用從舊VNode繼承到新VNode\n  instance.next = n2; // 將新VNode設定到next\n  instance.update(); // 觸發組件更新\n};\n```\n\n在 `updateComponent` 中，我們將新的 VNode（`n2`）設定到 `instance.next`，然後調用 `instance.update()`．\\\n這會觸發 `componentUpdateFn` 的執行．\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n    instance: ComponentInternalInstance,\n    initialVNode: VNode,\n    container: RendererElement\n  ) => {\n    const componentUpdateFn = () => {\n      const { render } = instance;\n      if (!instance.isMounted) {\n        const subTree = (instance.subTree = normalizeVNode(render()));\n        patch(null, subTree, container);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n      } else {\n        let { next, vnode } = instance;\n\n        if (next) {\n          // 當有來自父組件的更新請求時（例如，props 改變了）\n          next.el = vnode.el; // 將當前DOM元素引用繼承到新VNode\n          next.component = instance; // 將實例引用設定到新VNode\n          instance.vnode = next; // 將實例的「當前VNode」切換為新的\n          instance.next = null; // 已處理完畢，重置為null\n          updateProps(instance, next.props); // 用新的props更新實例的props\n        }\n        // 如果next不存在，則是由於組件自身響應式狀態變化而導致的重新渲染\n```\n\n當 `instance.next` 存在時，意味著有來自父組件的更新請求（如 props 改變）．\\\n在這種情況下，我們先將新 VNode 的資訊反映到實例中，然後再更新 props．\\\n當 `instance.next` 不存在時，則是由於組件自身內部狀態（響應式值）的變化而導致的重新渲染．\n\n如果螢幕更新了，那就沒問題．\\\n現在，您可以使用 props 將資料傳遞給組件！做得很好！\n\n![Component props flow in the browser](/figures/10-minimum-example/component-props/props-flow.png)\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system2)\n\n作為附註，雖然這不是必需的，但讓我們實現接收 kebab-case props 的能力，就像原始 Vue 中一樣．\\\n此時，創建一個名為 `~/packages/shared` 的目錄，並在其中創建一個名為 `general.ts` 的檔案．\\\n這是定義通用函式的地方，不僅適用於 `runtime-core` 和 `runtime-dom`．\\\n按照原始 Vue，讓我們實現 `hasOwn` 和 `camelize`．\n\n`~/packages/shared/general.ts`\n\n```ts\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nexport const hasOwn = (\n  val: object,\n  key: string | symbol,\n): key is keyof typeof val => hasOwnProperty.call(val, key)\n\nconst camelizeRE = /-(\\w)/g\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))\n}\n```\n\n讓我們在 `componentProps.ts` 中使用 `camelize`．\n\n```ts\nexport function updateProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n) {\n  const { props } = instance\n  // -------------------------------------------------------------- 這裡\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value\n  })\n}\n\nfunction setFullProps(\n  instance: ComponentInternalInstance,\n  rawProps: Data | null,\n  props: Data,\n) {\n  const options = instance.propsOptions\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      const value = rawProps[key]\n      // -------------------------------------------------------------- 這裡\n      // kebab -> camel\n      let camelKey\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value\n      }\n    }\n  }\n}\n```\n\n現在您應該也能夠處理 kebab-case 了．讓我們在遊樂場中檢查它．\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: { someMessage: string }) {\n    return () => h('div', {}, [`someMessage: ${props.someMessage}`])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(MyComponent, { 'some-message': state.message }, []),\n        h('button', { onClick: changeMessage }, ['change message']),\n      ])\n  },\n})\n```\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/052-component-emits.md",
    "content": "# 組件 Emits\n\n## 開發者介面\n\n繼 props 之後，讓我們實現 emits．\\\nemits 的實現相對簡單，所以會很快完成．\n\n在開發者介面方面，emits 將從 setup 函式的第二個參數接收．\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n## 實現\n\n與 props 類似，讓我們創建一個名為 `~/packages/runtime-core/componentEmits.ts` 的檔案並在那裡實現它．\\\n`~/packages/runtime-core/componentEmits.ts`\n\n```ts\nexport function emit(\n  instance: ComponentInternalInstance,\n  event: string,\n  ...rawArgs: any[]\n) {\n  const props = instance.vnode.props || {}\n  let args = rawArgs\n\n  let handler =\n    props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]\n\n  if (handler) handler(...args)\n}\n```\n\n`~/packages/shared/general.ts`\n\n```ts\nexport const capitalize = (str: string) =>\n  str.charAt(0).toUpperCase() + str.slice(1)\n\nexport const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``)\n```\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  emit: (event: string, ...args: any[]) => void\n}\n\nexport function createComponentInstance(\n  vnode: VNode,\n): ComponentInternalInstance {\n  const type = vnode.type as Component\n\n  const instance: ComponentInternalInstance = {\n    // .\n    // .\n    // .\n    emit: null!, // to be set immediately\n  }\n\n  instance.emit = emit.bind(null, instance)\n  return instance\n}\n```\n\n您可以將此傳遞給 setup 函式．\n\n`~/packages/runtime-core/componentOptions.ts`\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function // 接收 ctx.emit\n  render?: Function\n}\n```\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n    const instance: ComponentInternalInstance = (initialVNode.component =\n      createComponentInstance(initialVNode));\n\n    const { props } = instance.vnode;\n    initProps(instance, props);\n\n    const component = initialVNode.type as Component;\n    if (component.setup) {\n      // 傳遞 emit\n      instance.render = component.setup(instance.props, {\n        emit: instance.emit,\n      }) as InternalRenderFunction;\n    }\n```\n\n讓我們用我們之前假設的開發者介面示例來測試功能！  \n如果它正常工作，您現在可以使用 props/emit 在組件之間進行通訊！\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/050_component_system3)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/060-template-compiler.md",
    "content": "# 理解模板編譯器\n\n## 實際上，到目前為止我們已經擁有了運行所需的一切（？）\n\n到目前為止，我們已經實現了響應式系統，虛擬 DOM 和組件．\n雖然這些都非常小且不實用，但可以毫不誇張地說，我們已經理解了運行所需的整體配置元素．\n雖然每個元素本身的功能都不足，但感覺我們已經表面上過了一遍．\n\n從本章開始，我們將實現模板功能，使其更接近 Vue.js．但是，這些只是為了改善 DX，不會影響執行時．（嚴格來說，編譯器最佳化可能會有影響，但由於這不是重點，我們假設它沒有影響．）\\\n更具體地說，我們將擴展開發者介面以改善 DX，並「最終將其轉換為我們迄今為止製作的內部實現」．\n\n## 這次我們想要實現的開發者介面\n\n目前，開發者介面看起來像這樣．\n\n```ts\nconst MyComponent: Component = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n目前，View 部分是使用 h 函式建構的．我們希望能夠在 template 選項中編寫模板，使其更接近原始 HTML．\\\n但是，一次實現各種東西是困難的，所以讓我們從有限的功能集開始．\\\n現在，讓我們將其分為以下任務：\n\n1. 能夠渲染簡單的標籤，訊息和靜態屬性．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n2. 能夠渲染更複雜的 HTML．\n\n```ts\nconst app = createApp({\n  template: `\n    <div>\n      <p>hello</p>\n      <button> click me! </button>\n    </div>\n  `,\n})\n```\n\n3. 能夠使用在 setup 函式中定義的內容．\n\n```ts\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n\n    return { count, increment }\n  },\n\n  template: `\n    <div>\n      <p>count: {{ count }}</p>\n      <button v-on:click=\"increment\"> click me! </button>\n    </div>\n  `,\n})\n```\n\n我們將進一步將每個任務分為更小的部分，但讓我們大致分為這三個步驟．\n讓我們從步驟 1 開始．\n\n## 編譯器的作用\n\n現在，我們的目標開發者介面看起來像這樣．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n首先，讓我們談談什麼是編譯器．\n在編寫軟體時，您很快就會聽到「編譯器」這個詞．\n「編譯」意味著翻譯，在軟體領域，它通常用於表示從高級描述翻譯到低級描述．\\\n您還記得本書開頭的這個詞嗎？\n\n> 為了方便起見，我們將更接近原始 JS 的稱為「低級開發者介面」。\n> 而且，重要的是要注意「開始實現時，從低級部分開始」。\n> 這樣做的原因是，在許多情況下，高級描述被轉換為低級描述並執行。\n> 換句話說，1 和 2 最終在內部轉換為 3 的形式。\n> 這種轉換的實現稱為「編譯器」。\n\n那麼，為什麼我們需要這個叫做編譯器的東西呢？主要目的之一是「改善開發體驗」．\n至少，如果提供了一個有效的低級介面，就可以僅使用這些函式進行開發．\n但是，考慮與功能無關的各種部分可能會很麻煩和困難，描述可能難以理解．因此，我們將僅重新開發介面部分，考慮使用者的感受．\n\n在這方面，Vue.js 的目標是「像原始 HTML 一樣編寫，並使用 Vue 提供的功能（指令等）方便地編寫視圖」．\n而且，最終目標是 SFC．\\\n最近，隨著 jsx/tsx 的流行，Vue 也提供這些作為開發者介面的選項．但是，這次，讓我們嘗試實現 Vue 的原始模板．\n\n我已經用長篇文章解釋了它，但最終，我這次想要做的是實現將這樣的程式碼翻譯（編譯）的能力：\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\n```\n\n轉換為這樣：\n\n```ts\nconst app = createApp({\n  render() {\n    return h('p', { class: 'hello' }, ['Hello World'])\n  },\n})\n```\n\n為了進一步縮小範圍，就是這部分：\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\n讓我們分幾個階段逐步實現它．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/061-template-compiler-impl.md",
    "content": "# 實現模板編譯器\n\n## 實現方法\n\n基本方法是操作通過 template 選項傳遞的字串來生成特定函式．\\\n讓我們將編譯器分為三個元素．\n\n### 解析\n\n解析涉及從給定字串中提取必要資訊．您可以這樣想：\n\n```ts\nconst { tag, props, textContent } = parse(`<p class=\"hello\">Hello World</p>`)\nconsole.log(tag) // \"p\"\nconsole.log(prop) // { class: \"hello\" }\nconsole.log(textContent) // \"Hello World\"\n```\n\n### 程式碼生成\n\n程式碼生成基於解析結果生成程式碼（字串）．\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"h('p', { class: 'hello' }, ['Hello World']);\"\n```\n\n### 函式物件生成\n\n函式物件生成基於 codegen 生成的程式碼（字串）創建可執行函式．\\\n在 JavaScript 中，您可以使用 Function 建構函式從字串生成函式．\n\n```ts\nconst f = new Function('return 1')\nconsole.log(f()) // 1\n\n// 如果您想定義參數，可以這樣做\nconst add = new Function('a', 'b', 'return a + b')\nconsole.log(add(1, 1)) // 2\n```\n\n我們將使用這個來生成函式．\\\n這裡需要注意的一點是，生成的函式只能處理在其內部定義的變數，所以我們需要在其中包含 h 函式等函式的匯入．\n\n```ts\nimport * as runtimeDom from './runtime-dom'\nconst render = new Function('ChibiVue', code)(runtimeDom)\n```\n\n通過這樣做，我們可以將 runtimeDom 作為 ChibiVue 接收，並在 codegen 階段包含 h 函式，如下所示：\n\n```ts\nconst code = codegen({ tag, props, textContent })\nconsole.log(code) // \"return () => { const { h } = ChibiVue; return h('p', { class: 'hello' }, ['Hello World']); }\"\n```\n\n換句話說，之前我們說我們會這樣轉換：\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n// ↓\nh('p', { class: 'hello' }, ['Hello World'])\n```\n\n但準確地說，我們這樣轉換：\n\n```ts\n;`<p class=\"hello\">Hello World</p>`\n\n// ↓\n\nChibiVue => {\n  return () => {\n    const { h } = ChibiVue\n    return h('p', { class: 'hello' }, ['Hello World'])\n  }\n}\n```\n\n並傳遞 runtimeDom 來生成 render 函式．\\\ncodegen 的責任是生成以下字串：\n\n```ts\nconst code = `\n  return () => {\n      const { h } = ChibiVue;\n      return h(\"p\", { class: \"hello\" }, [\"Hello World\"]);\n  };\n`\n```\n\n## 實現\n\n一旦您理解了方法，讓我們實現它．\\\n在 `~/packages` 中創建一個名為 `compiler-core` 的目錄，並在其中創建 `index.ts`，`parse.ts` 和 `codegen.ts`．\n\n```sh\npwd # ~/\nmkdir packages/compiler-core\ntouch packages/compiler-core/index.ts\ntouch packages/compiler-core/parse.ts\ntouch packages/compiler-core/codegen.ts\n```\n\nindex.ts 像往常一樣只用於匯出．\n\n現在讓我們從 parse 開始實現．\n`packages/compiler-core/parse.ts`\n\n```ts\nexport const baseParse = (\n  content: string,\n): { tag: string; props: Record<string, string>; textContent: string } => {\n  const matched = content.match(/<(\\w+)\\s+([^>]*)>([^<]*)<\\/\\1>/)\n  if (!matched) return { tag: '', props: {}, textContent: '' }\n\n  const [_, tag, attrs, textContent] = matched\n\n  const props: Record<string, string> = {}\n  attrs.replace(/(\\w+)=[\"']([^\"']*)[\"']/g, (_, key: string, value: string) => {\n    props[key] = value\n    return ''\n  })\n\n  return { tag, props, textContent }\n}\n```\n\n雖然這是一個使用正規表示式的非常簡單的解析器，但對於第一次實現來說已經足夠了．\n\n接下來，讓我們生成程式碼．在 codegen.ts 中實現它．\\\n`packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = ({\n  tag,\n  props,\n  textContent,\n}: {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}): string => {\n  return `return () => {\n  const { h } = ChibiVue;\n  return h(\"${tag}\", { ${Object.entries(props)\n    .map(([k, v]) => `${k}: \"${v}\"`)\n    .join(', ')} }, [\"${textContent}\"]);\n}`\n}\n```\n\n現在，讓我們實現一個通過組合這些從模板生成函式字串的函式．\\\n創建一個名為 `packages/compiler-core/compile.ts` 的新檔案．\n\n`packages/compiler-core/compile.ts`\n\n```ts\nimport { generate } from './codegen'\nimport { baseParse } from './parse'\n\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template)\n  const code = generate(parseResult)\n  return code\n}\n```\n\n這應該不會太困難．實際上，`compiler-core` 的責任到此結束．\n\n## 執行時編譯器和建置過程編譯器\n\n實際上，Vue 有兩種類型的編譯器．\\\n一種是在執行時（在瀏覽器中）執行的編譯器，另一種是在建置過程中（如 Node.js）執行的編譯器．\\\n具體來說，執行時編譯器負責編譯 template 選項或作為 HTML 提供的模板，而建置過程編譯器負責編譯 SFC（或 JSX）．\\\n我們當前實現的 template 選項屬於前者．\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\n作為 HTML 提供的模板是一個開發者介面，您可以在 HTML 中編寫 Vue 模板．\\\n（通過 CDN 等快速將其合併到 HTML 中很方便．）\n\n```ts\nconst app = createApp()\napp.mount('#app')\n```\n\n```html\n<div id=\"app\">\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert('hello')\">click me!</button>\n</div>\n```\n\n這兩種都需要編譯，但編譯是在瀏覽器中執行的．\n\n另一方面，SFC 編譯在專案建置期間執行，執行時只存在編譯後的程式碼．\\\n（您需要在開發環境中設定 Vite 或 webpack 等打包器．）\n\n```vue\n<!-- App.vue -->\n<script>\nexport default {}\n</script>\n\n<template>\n  <p class=\"hello\">Hello World</p>\n  <button @click=\"() => alert(\"hello\")\">click me!</button>\n</template>\n```\n\n```ts\nimport App from 'App.vue'\nconst app = createApp(App)\napp.mount('#app')\n```\n\n```html\n<div id=\"app\"></div>\n```\n\n需要注意的重要一點是，兩個編譯器共享公共處理．\\\n這個公共部分的原始碼在 `compiler-core` 目錄中實現．\\\n執行時編譯器和 SFC 編譯器分別在 `compiler-dom` 和 `compiler-sfc` 目錄中實現．\\\n請再次查看這個圖表．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\n## 繼續實現\n\n我們跳得有點快，但讓我們繼續實現．\\\n雖然我想實現 `packages/index.ts`，但有一些準備工作要做，所以讓我們先做那個．\\\n準備工作是在 `packages/runtime-core/component.ts` 中實現一個變數來保存編譯器本身，以及一個註冊函式．\n\n`packages/runtime-core/component.ts`\n\n```ts\ntype CompileFunction = (template: string) => InternalRenderFunction\nlet compile: CompileFunction | undefined\n\nexport function registerRuntimeCompiler(_compile: any) {\n  compile = _compile\n}\n```\n\n現在，讓我們在 `packages/index.ts` 中生成函式並註冊它．\n\n```ts\nimport { compile } from './compiler-dom'\nimport { InternalRenderFunction, registerRuntimeCompiler } from './runtime-core'\nimport * as runtimeDom from './runtime-dom'\n\nfunction compileToFunction(template: string): InternalRenderFunction {\n  const code = compile(template)\n  return new Function('ChibiVue', code)(runtimeDom)\n}\n\nregisterRuntimeCompiler(compileToFunction)\n\nexport * from './runtime-core'\nexport * from './runtime-dom'\nexport * from './reactivity'\n```\n\n※ 不要忘記從 `runtime-dom` 匯出 `h` 函式，因為它需要包含在 `runtimeDom` 中．\n\n```ts\nexport { h } from '../runtime-core'\n```\n\n現在編譯器已註冊，讓我們實際執行編譯．\\\n由於組件選項類型中需要模板，讓我們現在添加模板．\n\n```ts\nexport type ComponentOptions = {\n  props?: Record<string, any>\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function\n  render?: Function\n  template?: string // 添加\n}\n```\n\n現在，讓我們編譯重要部分．\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  const instance: ComponentInternalInstance = (initialVNode.component =\n    createComponentInstance(initialVNode))\n\n  // ----------------------- 從這裡\n  const { props } = instance.vnode\n  initProps(instance, props)\n  const component = initialVNode.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n  // ----------------------- 到這裡\n\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\n我們將在 `packages/runtime-core/component.ts` 中提取上述部分．\n\n`packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n}\n```\n\n`packages/runtime-core/renderer.ts`\n\n```ts\nconst mountComponent = (initialVNode: VNode, container: RendererElement) => {\n  // prettier-ignore\n  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode));\n  setupComponent(instance)\n  setupRenderEffect(instance, initialVNode, container)\n}\n```\n\n現在，讓我們在 `setupComponent` 函式內部執行編譯．\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    instance.render = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n  }\n\n  // ------------------------ 這裡\n  if (compile && !component.render) {\n    const template = component.template ?? ''\n    if (template) {\n      instance.render = compile(template)\n    }\n  }\n}\n```\n\n現在，我們應該能夠使用 `template` 選項編譯簡單的 HTML．\\\n讓我們在遊樂場中試試！\n\n```ts\nconst app = createApp({ template: `<p class=\"hello\">Hello World</p>` })\napp.mount('#app')\n```\n\n![Simple template compiler output before cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-before.png)\n\n看起來工作正常．\\\n讓我們嘗試做一些更改，看看它們是否得到反映．\n\n```ts\nconst app = createApp({\n  template: `<b class=\"hello\" style=\"color: red;\">Hello World!!</b>`,\n})\napp.mount('#app')\n```\n\n![Simple template compiler output after cleanup](/figures/10-minimum-example/template-compiler-impl/simple-template-compiler-after.png)\n\n看起來實現正確！\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/070-more-complex-parser.md",
    "content": "# 我想編寫更複雜的 HTML\n\n## 我想編寫更複雜的 HTML\n\n在當前狀態下，我只能表達標籤的名稱和屬性，以及文字的內容．\\\n因此，我想能夠在模板中編寫更複雜的 HTML．\\\n具體來說，我想能夠編譯這樣的模板：\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n\n  `,\n})\napp.mount('#app')\n```\n\n但是，用正規表示式解析如此複雜的 HTML 是困難的．\\\n所以，從這裡開始，我將認真實現一個解析器．\n\n## AST 的介紹\n\n為了實現一個成熟的編譯器，我將引入一個叫做 AST（抽象語法樹）的東西．\\\nAST 代表抽象語法樹，顧名思義，它是表示語法的樹結構的資料表示．\\\n這是在實現各種編譯器時出現的概念，不僅僅是 Vue.js．\\\n在許多情況下（在語言處理系統中），「解析」指的是將其轉換為這種稱為 AST 的表示．\\\nAST 的定義由每種語言定義．\\\n例如，您熟悉的 JavaScript 由稱為 [estree](https://github.com/estree/estree) 的 AST 表示，原始碼字串根據此定義進行解析．\n\n我試圖以一種酷的方式解釋它，但在圖像方面，它只是我們迄今為止實現的 parse 函式返回類型的正式定義．\\\n目前，parse 函式的返回值如下：\n\n```ts\ntype ParseResult = {\n  tag: string\n  props: Record<string, string>\n  textContent: string\n}\n```\n\n讓我們擴展這個並定義它，以便可以執行更複雜的表達式．\n\n創建一個新檔案 `~/packages/compiler-core/ast.ts`．\\\n我將在編寫程式碼時解釋，因為它有點長．\n\n```ts\n// 這表示節點的類型。\n// 應該注意的是，這裡的 Node 不是指 HTML Node，而是指這個模板編譯器處理的粒度。\n// 所以，不僅 Element 和 Text，Attribute 也被視為一個 Node。\n// 這與 Vue.js 的設計一致，在將來實現指令時會很有用。\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  ATTRIBUTE,\n}\n\n// 所有 Node 都有 type 和 loc。\n// loc 代表位置，保存關於這個 Node 在原始碼（模板字串）中對應位置的資訊。\n// （例如，哪一行和行上的哪個位置）\nexport interface Node {\n  type: NodeTypes\n  loc: SourceLocation\n}\n\n// Element 的 Node。\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string // 例如 \"div\"\n  props: Array<AttributeNode> // 例如 { name: \"class\", value: { content: \"container\" } }\n  children: TemplateChildNode[]\n  isSelfClosing: boolean // 例如 <img /> -> true\n}\n\n// ElementNode 擁有的 Attribute。\n// 它可以表達為只是 Record<string, string>，\n// 但它被定義為像 Vue 一樣具有 name(string) 和 value(TextNode)。\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE\n  name: string\n  value: TextNode | undefined\n}\n\nexport type TemplateChildNode = ElementNode | TextNode\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT\n  content: string\n}\n\n// 關於位置的資訊。\n// Node 有這個資訊。\n// start 和 end 包含位置資訊。\n// source 包含實際程式碼（字串）。\nexport interface SourceLocation {\n  start: Position\n  end: Position\n  source: string\n}\n\nexport interface Position {\n  offset: number // 從檔案開始\n  line: number\n  column: number\n}\n```\n\n這是我們這次將要處理的 AST．\\\n在 parse 函式中，我們將實現將模板字串轉換為這個 AST．\n\n## 成熟解析器的實現\n\n::: warning\n在 2023 年 11 月下旬，在 [vuejs/core#9674](https://github.com/vuejs/core/pull/9674) 中進行了效能改進的重大重寫．  \n這些更改在 2023 年 12 月下旬作為 [Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) 發布．  \n請注意，這本線上書籍參考的是此重寫之前的實現．  \n我們計劃在適當的時機相應地更新這本線上書籍．\n:::\n\n在 `~/packages/compiler-core/parse.ts` 中實現它．\n即使我說它是成熟的，你也不必太緊張．\\\n基本上，你所做的就是在讀取字串時生成 AST，並使用分支和迴圈．\\\n原始碼會有點長，但我認為在程式碼庫中解釋會更容易理解．所以讓我們這樣進行．\\\n請通過閱讀原始碼來嘗試理解細節．\n\n刪除您迄今為止實現的 baseParse 的內容，並按如下方式更改返回類型：\n\n```ts\nimport { TemplateChildNode } from './ast'\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  // TODO:\n  return { children: [] }\n}\n```\n\n## Context\n\n首先，讓我們實現解析期間使用的狀態．\n\\我們將其命名為 `ParserContext`，並在解析期間在這裡收集必要的資訊．\\\n最終，我認為它也會保存解析器配置選項等．\n\n```ts\nexport interface ParserContext {\n  // 原始模板字串\n  readonly originalSource: string\n\n  source: string\n\n  // 此解析器正在讀取的當前位置\n  offset: number\n  line: number\n  column: number\n}\n\nfunction createParserContext(content: string): ParserContext {\n  return {\n    originalSource: content,\n    source: content,\n    column: 1,\n    line: 1,\n    offset: 0,\n  }\n}\n\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content) // 創建上下文\n\n  // TODO:\n  return { children: [] }\n}\n```\n\n## parseChildren\n\n在順序方面，解析按如下方式進行：(parseChildren) -> (parseElement 或 parseText)．\n\n雖然有點長，但讓我們從 parseChildren 的實現開始．\\\n解釋將在原始碼的註解中完成．\n\n```ts\nexport const baseParse = (\n  content: string,\n): { children: TemplateChildNode[] } => {\n  const context = createParserContext(content)\n  const children = parseChildren(context, []) // 解析子節點\n  return { children: children }\n}\n\nfunction parseChildren(\n  context: ParserContext,\n\n  // 由於 HTML 具有遞迴結構，我們將祖先元素保持為堆疊，並在每次嵌套到子元素中時推送它們。\n  // 當找到結束標籤時，parseChildren 結束並彈出祖先。\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (s[0] === '<') {\n      // 如果 s 以 \"<\" 開頭且下一個字元是字母，則將其解析為元素。\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors) // TODO: 稍後實現這個。\n      }\n    }\n\n    if (!node) {\n      // 如果不匹配上述條件，則將其解析為 TextNode。\n      node = parseText(context) // TODO: 稍後實現這個。\n    }\n\n    pushNode(nodes, node)\n  }\n\n  return nodes\n}\n\n// 確定解析子元素的 while 迴圈結束的函式\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // 如果 s 以 \"</\" 開頭且祖先的標籤名跟隨，它確定是否有閉合標籤（parseChildren 是否應該結束）。\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString)\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  // 如果 Text 類型的節點是連續的，它們會被合併。\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes)\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content\n      return\n    }\n  }\n\n  nodes.push(node)\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1]\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, '</') &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || '>')\n  )\n}\n```\n\n接下來，讓我們實現 parseElement 和 parseText．\n\n::: tip 關於 isEnd 迴圈\n在 isEnd 中，有一個迴圈過程，使用 startsWithEndTagOpen 檢查 's' 是否以 ancestors 陣列中每個元素的閉合標籤開頭．\n\n```ts\nfunction isEnd(context: ParserContext, ancestors: ElementNode[]): boolean {\n  const s = context.source\n\n  // 如果 s 以 </ 開頭且祖先的標籤名跟隨，它確定是否有閉合標籤（parseChildren 是否應該結束）。\n  if (startsWith(s, '</')) {\n    for (let i = ancestors.length - 1; i >= 0; --i) {\n      if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n        return true\n      }\n    }\n  }\n\n  return !s\n}\n```\n\n但是，如果您需要檢查 's' 是否以閉合標籤開頭，只檢查 ancestors 中的最後一個元素應該就足夠了．\\\n雖然這部分程式碼在解析器的最近重寫中被消除了，但將 Vue 3.3 程式碼修改為只檢查 ancestors 中的最後一個元素仍然會導致所有正面測試成功通過．\n:::\n\n## parseText\n\n首先，讓我們從簡單的 parseText 開始．\\\n它有點長，因為它還實現了一些不僅在 parseText 中使用，而且在其他函式中也使用的實用程式．\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  // 讀取直到 \"<\"（無論它是開始還是結束標籤），並根據讀取了多少字元計算 Text 資料結束點的索引。\n  const endToken = '<'\n  let endIndex = context.source.length\n  const index = context.source.indexOf(endToken, 1)\n  if (index !== -1 && endIndex > index) {\n    endIndex = index\n  }\n\n  const start = getCursor(context) // 用於 loc\n\n  // 根據 endIndex 的資訊解析 Text 資料。\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n\n// 根據內容和長度提取文字。\nfunction parseTextData(context: ParserContext, length: number): string {\n  const rawText = context.source.slice(0, length)\n  advanceBy(context, length)\n  return rawText\n}\n\n// -------------------- 以下是實用程式（也在 parseElement 等中使用） --------------------\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context\n  advancePositionWithMutation(context, source, numberOfCharacters)\n  context.source = source.slice(numberOfCharacters)\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\n// 雖然有點長，但它只是計算位置。\n// 它破壞性地更新作為參數接收的 pos 物件。\nfunction advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0\n  let lastNewLinePos = -1\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++\n      lastNewLinePos = i\n    }\n  }\n\n  pos.offset += numberOfCharacters\n  pos.line += linesCount\n  pos.column =\n    lastNewLinePos === -1\n      ? pos.column + numberOfCharacters\n      : numberOfCharacters - lastNewLinePos\n\n  return pos\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context\n  return { column, line, offset }\n}\n\nfunction getSelection(\n  context: ParserContext,\n  start: Position,\n  end?: Position,\n): SourceLocation {\n  end = end || getCursor(context)\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  }\n}\n```\n\n## parseElement\n\n接下來是元素的解析．\n元素的解析主要包括解析開始標籤，解析子節點和解析結束標籤．\\\n開始標籤的解析進一步分為標籤名和屬性．\\\n讓我們首先創建一個框架來解析開始標籤的前半部分，子節點和結束標籤．\n\n```ts\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // 開始標籤。\n  const element = parseTag(context, TagType.Start) // TODO:\n\n  // 如果它是像 <img /> 這樣的自閉合元素，我們在這裡結束（因為沒有子元素或結束標籤）。\n  if (element.isSelfClosing) {\n    return element\n  }\n\n  // 子元素。\n  ancestors.push(element)\n  const children = parseChildren(context, ancestors)\n  ancestors.pop()\n\n  element.children = children\n\n  // 結束標籤。\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End) // TODO:\n  }\n\n  return element\n}\n```\n\n這裡沒有什麼特別困難的．\\\n`parseChildren` 函式是遞迴的（因為 `parseElement` 被 `parseChildren` 呼叫）．\\\n我們在前後操作 `ancestors` 資料結構作為堆疊．\n\n讓我們實現 `parseTag`．\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // 標籤打開。\n  const start = getCursor(context)\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!\n  const tag = match[1]\n\n  advanceBy(context, match[0].length)\n  advanceSpaces(context)\n\n  // 屬性。\n  let props = parseAttributes(context, type)\n\n  // 標籤關閉。\n  let isSelfClosing = false\n\n  // 如果下一個字元是 \"/>\"，它是一個自閉合標籤。\n  isSelfClosing = startsWith(context.source, '/>')\n  advanceBy(context, isSelfClosing ? 2 : 1)\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    props,\n    children: [],\n    isSelfClosing,\n    loc: getSelection(context, start),\n  }\n}\n\n// 解析整個屬性（多個屬性）。\n// 例如 `id=\"app\" class=\"container\" style=\"color: red\"`\nfunction parseAttributes(\n  context: ParserContext,\n  type: TagType,\n): AttributeNode[] {\n  const props = []\n  const attributeNames = new Set<string>()\n\n  // 繼續讀取直到標籤結束。\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, '>') &&\n    !startsWith(context.source, '/>')\n  ) {\n    const attr = parseAttribute(context, attributeNames)\n\n    if (type === TagType.Start) {\n      props.push(attr)\n    }\n\n    advanceSpaces(context) // 跳過空格。\n  }\n\n  return props\n}\n\ntype AttributeValue =\n  | {\n      content: string\n      loc: SourceLocation\n    }\n  | undefined\n\n// 解析單個屬性。\n// 例如 `id=\"app\"`\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode {\n  // 名稱。\n  const start = getCursor(context)\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!\n  const name = match[0]\n\n  nameSet.add(name)\n\n  advanceBy(context, name.length)\n\n  // 值\n  let value: AttributeValue = undefined\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context)\n    advanceBy(context, 1)\n    advanceSpaces(context)\n    value = parseAttributeValue(context)\n  }\n\n  const loc = getSelection(context, start)\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  }\n}\n\n// 解析屬性的值。\n// 此實現允許解析值，無論它們是單引號還是雙引號。\n// 它只是提取引號中包含的值。\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context)\n  let content: string\n\n  const quote = context.source[0]\n  const isQuoted = quote === `\"` || quote === `'`\n  if (isQuoted) {\n    // 引用值。\n    advanceBy(context, 1)\n\n    const endIndex = context.source.indexOf(quote)\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length)\n    } else {\n      content = parseTextData(context, endIndex)\n      advanceBy(context, 1)\n    }\n  } else {\n    // 未引用\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source)\n    if (!match) {\n      return undefined\n    }\n    content = parseTextData(context, match[0].length)\n  }\n\n  return { content, loc: getSelection(context, start) }\n}\n```\n\n## 完成解析器實現後\n\n我寫了很多程式碼，比平時多．（最多只有大約 300 行）\\\n我認為在這裡閱讀實現比用特殊詞彙解釋更好，所以請反覆閱讀．\\\n雖然我寫了很多，但基本上它是通過讀取字串推進分析的直接任務，沒有特別困難的技術．\n\n到現在，您應該能夠生成 AST．讓我們檢查解析是否正常工作．\\\n但是，由於 codegen 部分尚未實現，我們這次將輸出到控制台進行確認．\n\n```ts\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\napp.mount('#app')\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim()) // 修剪模板\n  console.log(\n    '🚀 ~ file: compile.ts:6 ~ baseCompile ~ parseResult:',\n    parseResult,\n  )\n\n  // TODO: codegen\n  // const code = generate(parseResult);\n  // return code;\n  return ''\n}\n```\n\n螢幕不會顯示任何內容，但讓我們檢查控制台．\n\n![AST output for complex HTML](/figures/10-minimum-example/more-complex-parser/complex-html-ast.png)\n\n看起來解析進展順利．\\\n現在，讓我們基於生成的 AST 繼續實現 codegen．\n\n## 基於 AST 生成渲染函式\n\n現在我們已經實現了一個成熟的解析器，讓我們創建一個可以應用於它的程式碼生成器．\\\n但是，在這一點上，不需要複雜的實現．\\\n我將首先向您展示程式碼．\n\n```ts\nimport { ElementNode, NodeTypes, TemplateChildNode, TextNode } from './ast'\n\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render() {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node)\n    case NodeTypes.TEXT:\n      return genText(node)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) => `${name}: \"${value?.content}\"`)\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genText = (text: TextNode): string => {\n  return `\\`${text.content}\\``\n}\n```\n\n使用上述程式碼，您可以創建有效的東西．\\\n取消註解解析器章節中被註解掉的部分並檢查實際操作．\\\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(template: string) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult)\n  return code\n}\n```\n\nplayground\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\n![Rendered template result in the browser](/figures/10-minimum-example/more-complex-parser/render-template-result.png)\n\n怎麼樣？看起來我們可以很好地渲染螢幕．\n\n讓我們為螢幕添加一些動作．\\\n由於我們還沒有實現模板綁定，我們將直接操作 DOM．\n\n```ts\nexport type ComponentOptions = {\n  // .\n  // .\n  // .\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | void // 也允許 void\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nimport { createApp } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    // 使用 Promise.resolve 延遲處理，以便在掛載後可以執行 DOM 操作\n    Promise.resolve().then(() => {\n      const btn = document.getElementById('btn')\n      btn &&\n        btn.addEventListener('click', () => {\n          const h2 = document.getElementById('hello')\n          h2 && (h2.textContent += '!')\n        })\n    })\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2 id=\"hello\">Hello, chibivue!</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button id=\"btn\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n\napp.mount('#app')\n```\n\n讓我們確保它正常工作．\\\n怎麼樣？雖然功能有限，但它越來越接近通常的 Vue 開發者介面．\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler2)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/080-template-binding.md",
    "content": "# 資料綁定\n\n## 想要綁定到模板\n\n目前，我們直接操作 DOM，因此無法利用響應式系統或虛擬 DOM．\\\n實際上，我們希望在模板部分編寫事件處理程式和文字內容．這就是宣告式 UI 的樂趣所在．\\\n我們的目標是實現如下的開發者介面．\n\n```ts\nimport { createApp, reactive, h } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render() {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', {}, `message: ${this.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', {}, [h('b', {}, 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onclick: this.changeMessage }, 'click me!'),\n      h(\n        'style',\n        {},\n        `\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      `,\n      ),\n    ])\n  },\n})\n\napp.mount('#app')\n```\n\n現在，我想能夠在模板中處理從 `setup` 函式返回的值．\\\n從現在開始，我將把這稱為「模板綁定」或簡稱「綁定」．\\\n我將實現綁定，但在實現事件處理程式和 mustache 語法之前，有幾件事我想做．\n\n我提到了從 `setup` 返回的值，但目前 `setup` 的返回值要麼是 `undefined`，要麼是一個函式（渲染函式）．\\\n作為實現綁定的準備，我需要修改它，使 `setup` 可以返回狀態和其他值，並且這些值可以作為組件資料儲存．\n\n```ts\nexport type ComponentOptions = {\n  setup?: (\n    props: Record<string, any>,\n    ctx: { emit: (event: string, ...args: any[]) => void },\n  ) => Function | Record<string, unknown> | void\n  // 允許返回 Record<string, unknown>\n  // .\n  // .\n  // .\n}\n```\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupState: Data // 將 setup 的結果作為物件儲存在這裡\n}\n```\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  const { props } = instance.vnode\n  initProps(instance, props)\n\n  const component = instance.type as Component\n  if (component.setup) {\n    const setupResult = component.setup(instance.props, {\n      emit: instance.emit,\n    }) as InternalRenderFunction\n\n    // 根據 setupResult 的類型進行分支\n    if (typeof setupResult === 'function') {\n      instance.render = setupResult\n    } else if (typeof setupResult === 'object' && setupResult !== null) {\n      instance.setupState = setupResult\n    } else {\n      // do nothing\n    }\n  }\n  // .\n  // .\n  // .\n}\n```\n\n從現在開始，我將把在 `setup` 中定義的資料稱為 `setupState`．\n\n現在，在實現編譯器之前，讓我們思考如何將 `setupState` 綁定到模板．\\\n之前，我們這樣綁定 `setupState`：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return () => h('div', {}, [state.message])\n  },\n})\n```\n\n嗯，這實際上不是真正的綁定，而是渲染函式簡單地形成閉包並引用變數．\\\n然而，這次，由於 setup 選項和渲染函式在概念上是不同的，我們需要找到一種方法將 setup 資料傳遞給渲染函式．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  // 這將被轉換為渲染函式\n  template: '<div>{{ state.message }}</div>',\n})\n```\n\n`template` 使用 `h` 函式編譯為渲染函式並分配給 `instance.render`．\\\n因此，它等價於以下程式碼：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render() {\n    return h('div', {}, [state.message])\n  },\n})\n```\n\n自然地，變數 `state` 在渲染函式內部沒有定義．\\\n現在，我們如何引用 `state` 變數？\n\n## 使用 `with` 語句\n\n總之，我們可以使用 `with` 語句來實現所需的結果：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    return { state }\n  },\n\n  render(ctx) {\n    with (ctx) {\n      return h('div', {}, [state.message])\n    }\n  },\n})\n```\n\n我相信有很多人不熟悉 `with` 語句．\n\n這是有充分理由的，這個功能已被棄用．\n\n根據 MDN：\n\n> 雖然仍然被一些瀏覽器支援，但它已從 Web 標準中棄用。但是，它可能仍在用於各種目的，例如與遺留程式碼的相容性。避免使用它，如果可能的話更新現有程式碼。\n\n因此，建議避免使用它．\n\n我們不知道 Vue.js 的實現將來會如何變化，但由於 Vue.js 3 使用 `with` 語句，我們將在此實現中使用它．\n\n稍微說一下，Vue.js 中並非所有內容都使用 `with` 語句實現．\\\n在處理單檔案組件（SFC）中的模板時，它是在不使用 `with` 語句的情況下實現的．\\\n我們將在後面的章節中介紹這一點，但現在，讓我們考慮使用 `with` 來實現它．\n\n---\n\n現在，讓我們回顧一下 `with` 語句的行為．\n`with` 語句擴展語句的作用域鏈．\n\n它的行為如下：\n\n```ts\nconst obj = { a: 1, b: 2 }\n\nwith (obj) {\n  console.log(a, b) // 1, 2\n}\n```\n\n通過將包含 `state` 的父物件作為參數傳遞給 `with`，我們可以引用 `state` 變數．\n\n在這種情況下，我們將把 `setupState` 視為父物件．\\\n實際上，不僅是 `setupState`，來自 `props` 的資料和在 Options API 中定義的資料也應該是可存取的．\\\n但是，現在，我們只考慮使用來自 `setupState` 的資料．\n（我們將在後面的部分中介紹這部分的實現，因為它不是最小實現的一部分．）\n\n總結我們這次想要實現的內容，我們想要編譯以下模板：\n\n```html\n<div>\n  <p>{{ state.message }}</p>\n  <button @click=\"changeMessage\">click me</button>\n</div>\n```\n\n轉換為以下函式：\n\n```ts\n_ctx => {\n  with (_ctx) {\n    return h('div', {}, [\n      h('p', {}, [state.message]),\n      h('button', { onClick: changeMessage }, ['click me']),\n    ])\n  }\n}\n```\n\n並將 `setupState` 傳遞給這個函式：\n\n```ts\nconst setupState = setup()\nrender(setupState)\n```\n\n## 實現 Mustache 語法\n\n首先，讓我們實現 Mustache 語法．\\\n像往常一樣，我們將考慮 AST，實現解析器，然後實現程式碼生成器．\\\n目前，作為 AST 一部分定義的唯一節點是 `Element`，`Text` 和 `Attribute`．\\\n由於我們想要定義 Mustache 語法，直覺上有一個叫做 `Mustache` 的 AST 是有意義的．\\\n為此，我們將使用 `Interpolation` 節點．\\\nInterpolation 有「插值」或「插入」等含義．\\\n因此，我們這次將處理的 AST 將如下所示：\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION, // 添加\n}\n\nexport type TemplateChildNode = ElementNode | TextNode | InterpolationNode // 添加 InterpolationNode\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // Mustache 內部編寫的內容（在這種情況下，在 setup 中定義的單個變數名將放在這裡）\n}\n```\n\n現在 AST 已經實現，讓我們繼續實現解析器．\\\n當我們找到字串 <span v-pre>`{{`</span> 時，我們將把它解析為 `Interpolation`．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[]\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | undefined = undefined;\n\n    if (startsWith(s, \"{{\")) { // 這裡\n      node = parseInterpolation(context);\n    } else if (s[0] === \"<\") {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context, ancestors);\n      }\n    }\n    // .\n    // .\n    //\n    }\n```\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  const [open, close] = ['{{', '}}']\n  const closeIndex = context.source.indexOf(close, open.length)\n  if (closeIndex === -1) return undefined\n\n  const start = getCursor(context)\n  advanceBy(context, open.length)\n\n  const innerStart = getCursor(context)\n  const innerEnd = getCursor(context)\n  const rawContentLength = closeIndex - open.length\n  const rawContent = context.source.slice(0, rawContentLength)\n  const preTrimContent = parseTextData(context, rawContentLength)\n\n  const content = preTrimContent.trim()\n\n  const startOffset = preTrimContent.indexOf(content)\n\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset)\n  }\n  const endOffset =\n    rawContentLength - (preTrimContent.length - content.length - startOffset)\n  advancePositionWithMutation(innerEnd, rawContent, endOffset)\n  advanceBy(context, close.length)\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n有些情況下 <span v-pre>`{{`</span> 出現在文字中，所以我們將對 `parseText` 進行一些修改．\n\n```ts\nfunction parseText(context: ParserContext): TextNode {\n  const endTokens = ['<', '{{'] // 如果 <span v-pre>`{{`</span> 出現，parseText 結束\n\n  let endIndex = context.source.length\n\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1)\n    if (index !== -1 && endIndex > index) {\n      endIndex = index\n    }\n  }\n\n  const start = getCursor(context)\n  const content = parseTextData(context, endIndex)\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n對於那些到目前為止已經實現了解析器的人來說，應該沒有特別困難的部分．\\\n它只是搜尋 <span v-pre>`{{`</span> 並讀取直到 <span v-pre>`}}`</span> 出現，生成 AST．\\\n如果沒有找到 <span v-pre>`}}`</span>，它返回 undefined 並在 parseText 的分支中將其解析為文字．\n\n讓我們輸出到控制台或其他地方，以確保解析正常工作．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Interpolation AST output](/figures/10-minimum-example/template-binding/parse-interpolation-ast.png)\n\n看起來不錯！\n\n現在讓我們基於這個 AST 實現綁定．\\\n用 with 語句包裝渲染函式的內容．\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n\nconst genNode = (node: TemplateChildNode): string => {\n  switch (node.type) {\n    // .\n    // .\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node)\n    // .\n    // .\n  }\n}\n\nconst genInterpolation = (node: InterpolationNode): string => {\n  return `${node.content}`\n}\n```\n\n最後，在執行渲染函式時，將 `setupState` 作為參數傳遞．\\\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport type InternalRenderFunction = {\n  (ctx: Data): VNodeChild // 接受 ctx 作為參數\n}\n```\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n) => {\n  const componentUpdateFn = () => {\n    const { render, setupState } = instance\n    if (!instance.isMounted) {\n      // .\n      // .\n      // .\n      const subTree = (instance.subTree = normalizeVNode(render(setupState))) // 傳遞 setupState\n      // .\n      // .\n      // .\n    } else {\n      // .\n      // .\n      // .\n      const nextTree = normalizeVNode(render(setupState)) // 傳遞 setupState\n      // .\n      // .\n      // .\n    }\n  }\n}\n```\n\n如果你已經走到這一步，你應該能夠渲染了．讓我們檢查一下！\n\n![Rendered interpolation result in the browser](/figures/10-minimum-example/template-binding/render-interpolation-result.png)\n\n這完成了第一個綁定！\n\n## 第一個指令\n\n接下來是事件處理程式．\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(({ name, value }) =>\n      // 如果是 @click，將 props 名稱轉換為 onClick\n      name === '@click'\n        ? `onClick: ${value?.content}`\n        : `${name}: \"${value?.content}\"`,\n    )\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n```\n\n讓我們檢查操作．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n你做到了！做得好！完成了！\n\n我想這樣說，但實現還不夠乾淨，所以我想稍微重構一下．\\\n由於 `@click` 被歸類為「指令」名稱，很容易想像將來實現 `v-bind` 和 `v-model`．\\\n所以讓我們在 AST 中將其表示為 `DIRECTIVE` 並將其與簡單的 `ATTRIBUTE` 區分開來．\n\n像往常一樣，讓我們按照 AST -> parse -> codegen 的順序實現它．\n\n```ts\nexport const enum NodeTypes {\n  ELEMENT,\n  TEXT,\n  INTERPOLATION,\n\n  ATTRIBUTE,\n  DIRECTIVE, // 添加\n}\n\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode> // props 是 AttributeNode 和 DirectiveNode 聯合的陣列\n  // .\n  // .\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  // 表示 `v-name:arg=\"exp\"` 的格式。\n  // 例如，對於 `v-on:click=\"increment\"`，它將是 { name: \"on\", arg: \"click\", exp=\"increment\" }\n  name: string\n  arg: string\n  exp: string\n}\n```\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>\n): AttributeNode | DirectiveNode {\n  // 名稱。\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // 值\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // --------------------------------------------------- 從這裡\n  // 指令\n  const loc = getSelection(context, start);\n  if (/^(v-[A-Za-z0-9-]|@)/.test(name)) {\n    const match =\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name\n      )!;\n\n    let dirName = match[1] || (startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg = \"\";\n\n    if (match[2]) arg = match[2];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value?.content ?? \"\",\n      loc,\n      arg,\n    };\n  }\n  // --------------------------------------------------- 到這裡\n  // .\n  // .\n  // .\n```\n\n```ts\nconst genElement = (el: ElementNode): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop))\n    .join(', ')}}, [${el.children.map(it => genNode(it)).join(', ')}])`\n}\n\nconst genProp = (prop: AttributeNode | DirectiveNode): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${prop.exp}`\n        default:\n          // TODO: 其他指令\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n```\n\n現在，讓我們在遊樂場中檢查操作．\\\n你應該能夠處理不僅 `@click`，還有 `v-on:click` 和其他事件．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n![Compiled directive result in the browser](/figures/10-minimum-example/template-binding/compile-directives-result.png)\n\n你做到了．\\\n我們越來越接近 Vue 了！\\\n有了這個，小模板的實現就完成了．做得好．\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/060_template_compiler3)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/090-prerequisite-knowledge-for-the-sfc.md",
    "content": "## 周邊知識\n\n## SFC 是如何實現的？\n\n現在，讓我們最終開始支援單檔案組件（SFC）．\\\n那麼，我們應該如何支援它呢？SFC 就像模板一樣，在開發期間使用，在執行時不存在．\\\n對於那些已經完成模板開發的人來說，我認為這只是如何編譯它的簡單問題．\n\n你只需要將以下 SFC 程式碼：\n\n```vue\n<script>\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>message: {{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n轉換為以下 JS 程式碼：\n\n```ts\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return { state, changeMessage }\n  },\n\n  render(_ctx) {\n    return h('div', { class: 'container', style: 'text-align: center' }, [\n      h('h2', `message: ${_ctx.state.message}`),\n      h('img', {\n        width: '150px',\n        src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png',\n      }),\n      h('p', [h('b', 'chibivue'), ' is the minimal Vue.js']),\n      h('button', { onClick: _ctx.changeMessage }, 'click me!'),\n    ])\n  },\n}\n```\n\n你可能會想知道樣式！但現在，讓我們忘記這一點，專注於模板和腳本．\\\n我們不會在最小範例中涵蓋 `script setup`．\n\n## 我們應該何時以及如何編譯？\n\n總之，「我們在建置工具解析相依性時編譯」．\n在大多數情況下，SFC 從其他檔案匯入和使用．\n此時，我們編寫一個外掛程式，在解析 `.vue` 檔案時編譯它並將結果綁定到應用程式．\n\n```ts\nimport App from './App.vue' // 匯入 App.vue 時編譯\n\nconst app = createApp(App)\napp.mount('#app')\n```\n\n有各種建置工具，但這次讓我們嘗試為 Vite 編寫一個外掛程式．\n\n由於可能很少有人從未編寫過 Vite 外掛程式，讓我們首先通過一個簡單的範例程式碼熟悉外掛程式實現．讓我們現在創建一個簡單的 Vue 專案．\n\n```sh\npwd # ~\npnpm dlx create-vite\n## ✔ Project name: … plugin-sample\n## ✔ Select a framework: › Vue\n## ✔ Select a variant: › TypeScript\n\ncd plugin-sample\nni\n```\n\n讓我們看看創建專案的 vite.config.ts 檔案．\n\n```ts\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue()],\n})\n```\n\n你可以看到它將 `@vitejs/plugin-vue` 添加到外掛程式中．\n實際上，當使用 Vite 創建 Vue 專案時，由於這個外掛程式，可以使用 SFC．\n這個外掛程式根據 Vite 外掛程式 API 實現 SFC 編譯器，並將 Vue 檔案編譯為 JS 檔案．\n讓我們嘗試在這個專案中創建一個簡單的外掛程式．\n\n```ts\nimport { defineConfig, Plugin } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [vue(), myPlugin()],\n})\n\nfunction myPlugin(): Plugin {\n  return {\n    name: 'vite:my-plugin',\n\n    transform(code, id) {\n      if (id.endsWith('.sample.js')) {\n        let result = ''\n\n        for (let i = 0; i < 100; i++) {\n          result += `console.log(\"HelloWorld from plugin! (${i})\");\\n`\n        }\n\n        result += code\n\n        return { code: result }\n      }\n    },\n  }\n}\n```\n\n我創建了一個名為 `myPlugin` 的外掛程式．\\\n由於它很簡單，我認為很多人不用解釋就能理解，但我還是會解釋一下以防萬一．\n\n外掛程式符合 Vite 要求的格式．\\\n有各種選項，但由於這是一個簡單的範例，我只使用了 `transform` 選項．\\\n我建議查看官方文件和其他資源以獲取更多資訊：https://vite.dev/guide/api-plugin\n\n在 `transform` 函式中，你可以接收 `code` 和 `id`．\\\n你可以將 `code` 視為檔案的內容，將 `id` 視為檔案名．\\\n作為返回值，你將結果放在 `code` 屬性中．\n你可以根據 `id` 為每種檔案類型編寫不同的處理，或修改 `code` 來重寫檔案的內容．\\\n在這種情況下，我為以 `*.sample.js` 結尾的檔案在檔案內容的開頭添加了 100 個控制台日誌．\\\n現在，讓我們實現一個範例 `plugin.sample.js` 並檢查它．\n\n```sh\npwd # ~/plugin-sample\ntouch src/plugin.sample.js\n```\n\n`~/plugin-sample/src/plugin.sample.js`\n\n```ts\nfunction fizzbuzz(n) {\n  for (let i = 1; i <= n; i++) {\n    i % 3 === 0 && i % 5 === 0\n      ? console.log('fizzbuzz')\n      : i % 3 === 0\n        ? console.log('fizz')\n        : i % 5 === 0\n          ? console.log('buzz')\n          : console.log(i)\n  }\n}\n\nfizzbuzz(Math.floor(Math.random() * 100) + 1)\n```\n\n`~/plugin-sample/src/main.ts`\n\n```ts\nimport { createApp } from 'vue'\nimport './style.css'\nimport App from './App.vue'\nimport './plugin.sample.js' // 添加\n\ncreateApp(App).mount('#app')\n```\n\n讓我們在瀏覽器中檢查它．\n\n```sh\npwd # ~/plugin-sample\npnpm dev\n```\n\n![Sample Vite plugin console output](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-console.png)\n\n![Sample Vite plugin transformed source](/figures/10-minimum-example/sfc-prerequisites/sample-vite-plugin-source.png)\n\n你可以看到原始碼已經被正確修改了．\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/091-parse-sfc.md",
    "content": "# 實現 SFC 解析器\n\n## 準備工作\n\n雖然這是我們之前創建的範例外掛程式，但讓我們刪除它，因為它不再需要了．\n\n```sh\npwd # ~\nrm -rf ./plugin-sample\n```\n\n另外，為了創建 Vite 外掛程式，請安裝主要的 Vite 套件．\n\n```sh\npwd # ~\npnpm add vite\n```\n\n這是外掛程式的主要部分，但由於這原本超出了 vuejs/core 的範圍，我們將在 `packages` 目錄中創建一個名為 `@extensions` 的目錄並在那裡實現它．\n\n```sh\npwd # ~\nmkdir -p packages/@extensions/vite-plugin-chibivue\ntouch packages/@extensions/vite-plugin-chibivue/index.ts\n```\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      return { code }\n    },\n  }\n}\n```\n\n現在，讓我們實現 SFC 編譯器．\\\n但是，沒有任何實質內容可能很難想像，所以讓我們實現一個遊樂場並在執行時進行．\\\n我們將創建一個簡單的 SFC 並載入它．\n\n```sh\npwd # ~\ntouch examples/playground/src/App.vue\n```\n\n`examples/playground/src/App.vue`\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n`playground/src/main.ts`\n\n```ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n`playground/vite.config.js`\n\n```ts\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineConfig } from 'vite'\n\nimport chibivue from '../../packages/@extensions/vite-plugin-chibivue'\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: path.resolve(dirname, '../../packages'),\n    },\n  },\n  plugins: [chibivue()],\n})\n```\n\n讓我們嘗試在這種狀態下啟動．\n\n![Vite error before the SFC plugin is implemented](/figures/10-minimum-example/parse-sfc/vite-error.png)\n\n當然，這會導致錯誤．做得好（？）．\n\n## 解決錯誤\n\n讓我們暫時解決錯誤．我們不會立即追求完美．\\\n首先，讓我們將 `transform` 的目標限制為 \"\\*.vue\"．\\\n我們可以像在範例中那樣使用 `id` 編寫分支語句，但由於 Vite 提供了一個名為 `createFilter` 的函式，讓我們使用它創建一個過濾器．\\\n（這沒有特別的原因．）\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\n我們創建了一個過濾器，如果是 Vue 檔案，則將檔案內容轉換為 `export default {}`．\\\n錯誤應該消失，螢幕應該不顯示任何內容．\n\n## 在 compiler-sfc 上實現解析器\n\n現在，這只是一個臨時解決方案，所以讓我們實現一個合適的解決方案．\\\nvite-plugin 的作用是使用 Vite 啟用轉換，所以解析和編譯在主 Vue 套件中．\\\n那就是 `compiler-sfc` 目錄．\n\n![Vue package dependency map](/figures/00-introduction/vue-core-components/package-dependency-overview.svg)\n\nhttps://github.com/vuejs/core/blob/main/.github/contributing.md#package-dependencies\n\nSFC 編譯器對於 Vite 和 Webpack 都是相同的．\\\n核心實現在 `compiler-sfc` 中．\n\n讓我們創建 `compiler-sfc`．\n\n```sh\npwd # ~\nmkdir packages/compiler-sfc\ntouch packages/compiler-sfc/index.ts\n```\n\n在 SFC 編譯中，SFC 由一個名為 `SFCDescriptor` 的物件表示．\n\n```sh\ntouch packages/compiler-sfc/parse.ts\n```\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { SourceLocation } from '../compiler-core'\n\nexport interface SFCDescriptor {\n  id: string\n  filename: string\n  source: string\n  template: SFCTemplateBlock | null\n  script: SFCScriptBlock | null\n  styles: SFCStyleBlock[]\n}\n\nexport interface SFCBlock {\n  type: string\n  content: string\n  loc: SourceLocation\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: 'template'\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: 'script'\n}\n\nexport declare interface SFCStyleBlock extends SFCBlock {\n  type: 'style'\n}\n```\n\n嗯，沒有什麼特別困難的．\\\n它只是一個表示 SFC 資訊的物件．\n\n在 `packages/compiler-sfc/parse.ts` 中，我們將把 SFC 檔案（字串）解析為 `SFCDescriptor`．\\\n你們中的一些人可能在想，「什麼？你在模板解析器上如此努力工作，現在你要創建另一個解析器...？這很麻煩．」但不要擔心．\\\n我們在這裡要實現的解析器並不是什麼大事．那是因為我們只是通過結合我們迄今為止創建的內容來分離模板，腳本和樣式．\n\n首先，作為準備，匯出我們之前創建的模板解析器．\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nimport { baseCompile, baseParse } from '../compiler-core'\n\nexport function compile(template: string) {\n  return baseCompile(template)\n}\n\n// 匯出解析器\nexport function parse(template: string) {\n  return baseParse(template)\n}\n```\n\n在 compiler-sfc 端保留這些介面．\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/compileTemplate.ts\n```\n\n`~/packages/compiler-sfc/compileTemplate.ts`\n\n```ts\nimport { TemplateChildNode } from '../compiler-core'\n\nexport interface TemplateCompiler {\n  compile(template: string): string\n  parse(template: string): { children: TemplateChildNode[] }\n}\n```\n\n然後，只需實現解析器．\n\n`packages/compiler-sfc/parse.ts`\n\n```ts\nimport { ElementNode, NodeTypes, SourceLocation } from '../compiler-core'\nimport * as CompilerDOM from '../compiler-dom'\nimport { TemplateCompiler } from './compileTemplate'\n\nexport interface SFCParseOptions {\n  filename?: string\n  sourceRoot?: string\n  compiler?: TemplateCompiler\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor\n}\n\nexport const DEFAULT_FILENAME = 'anonymous.vue'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    styles: [],\n  }\n\n  const ast = compiler.parse(source)\n  ast.children.forEach(node => {\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    switch (node.tag) {\n      case 'template': {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock\n        break\n      }\n      case 'script': {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock\n        descriptor.script = scriptBlock\n        break\n      }\n      case 'style': {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock)\n        break\n      }\n      default: {\n        break\n      }\n    }\n  })\n\n  return { descriptor }\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag\n\n  let { start, end } = node.loc\n  start = node.children[0].loc.start\n  end = node.children[node.children.length - 1].loc.end\n  const content = source.slice(start.offset, end.offset)\n\n  const loc = { source: content, start, end }\n  const block: SFCBlock = { type, content, loc }\n\n  return block\n}\n```\n\n我認為對於到目前為止已經實現了解析器的每個人來說都很容易．讓我們在外掛程式中實際解析 SFC．\n\n`~/packages/@extensions/vite-plugin-chibivue/index.ts`\n\n```ts\nimport { parse } from '../../compiler-sfc'\n\nexport default function vitePluginChibivue(): Plugin {\n  //.\n  //.\n  //.\n  return {\n    //.\n    //.\n    //.\n    transform(code, id) {\n      if (!filter(id)) return\n      const { descriptor } = parse(code, { filename: id })\n      console.log(\n        '🚀 ~ file: index.ts:14 ~ transform ~ descriptor:',\n        descriptor,\n      )\n      return { code: `export default {}` }\n    },\n  }\n}\n```\n\n這段程式碼在 Vite 執行的程序中執行，這意味著它在 Node 中執行，所以我認為控制台輸出會顯示在終端中．\n\n![SFC descriptor before parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-before.png)\n\n/_ 為簡潔起見省略 _/\n\n![SFC descriptor after parser update](/figures/10-minimum-example/parse-sfc/parse-sfc-descriptor-after.png)\n\n看起來解析成功了．做得好！\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler2)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/092-compile-sfc-template.md",
    "content": "# 編譯模板區塊\n\n## 切換編譯器\n\n`descriptor.script.content` 和 `descriptor.template.content` 包含每個部分的原始碼．\\\n讓我們成功編譯它們．讓我們從模板部分開始．\\\n我們已經有了模板編譯器．\\\n但是，正如你從以下程式碼中看到的，\n\n```ts\nexport const generate = ({\n  children,\n}: {\n  children: TemplateChildNode[]\n}): string => {\n  return `return function render(_ctx) {\n  with (_ctx) {\n    const { h } = ChibiVue;\n    return ${genNode(children[0])};\n  }\n}`\n}\n```\n\n這假設它將與 Function 建構函式一起使用，所以它在開頭包含 `return` 語句．\\\n在 SFC 編譯器中，我們只想生成渲染函式，所以讓我們使其能夠通過編譯器選項進行分支．\\\n讓我們使其能夠接收選項作為編譯器的第二個參數，並指定一個名為 `isBrowser` 的標誌．\\\n當這個變數為 `true` 時，它輸出假設將在執行時 `new` 的程式碼，當它為 `false` 時，它只是生成程式碼．\n\n```sh\npwd # ~\ntouch packages/compiler-core/options.ts\n```\n\n`packages/compiler-core/options.ts`\n\n```ts\nexport type CompilerOptions = {\n  isBrowser?: boolean\n}\n```\n\n`~/packages/compiler-dom/index.ts`\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption: Required<CompilerOptions> = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(template, defaultOption)\n}\n```\n\n`~/packages/compiler-core/compile.ts`\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const parseResult = baseParse(template.trim())\n  const code = generate(parseResult, option)\n  return code\n}\n```\n\n`~/packages/compiler-core/codegen.ts`\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n  const { h } = ChibiVue;\n  return ${genNode(children[0])};\n}`\n}\n```\n\n我還添加了匯入語句．我將其更改為將生成的原始碼添加到 `output` 陣列中．\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\\n\")\n\n      const { descriptor } = parse(code, { filename: id })\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { render }`)\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\n## 問題\n\n現在你應該能夠編譯渲染函式了．讓我們在瀏覽器的原始碼中檢查它．\n\n但是，有一個小問題．\n\n當將資料綁定到模板時，我認為你正在使用 `with` 語句．\\\n但是，由於 Vite 處理 ESM 的性質，它無法處理只在非嚴格模式（sloppy mode）下工作的程式碼，並且無法處理 `with` 語句．\\\n到目前為止，這還不是問題，因為我只是將包含 `with` 語句的程式碼（字串）傳遞給 Function 建構函式並在瀏覽器中使其成為函式，但現在它會拋出錯誤．\\\n你應該看到這樣的錯誤：\n\n> Strict mode code may not include a with statement\n\n這也在 Vite 官方文件中作為故障排除提示進行了描述．\n\n[Syntax Error / Type Error Occurs (Vite)](https://vite.dev/guide/troubleshooting.html#syntax-error-type-error-occurs)\n\n作為臨時解決方案，讓我們嘗試在非瀏覽器模式下生成不包含 `with` 語句的程式碼．\n\n具體來說，對於要綁定的資料，讓我們嘗試通過添加前綴 `_ctx.` 而不是使用 `with` 語句來控制它．\\\n由於這是一個臨時解決方案，它不是很嚴格，但我認為它通常會工作．\\\n（正確的解決方案將在後面的章節中實現．）\n\n```ts\nexport const generate = (\n  {\n    children,\n  }: {\n    children: TemplateChildNode[]\n  },\n  option: Required<CompilerOptions>,\n): string => {\n  // 當 `isBrowser` 為 false 時生成不包含 `with` 語句的程式碼\n  return `${option.isBrowser ? 'return ' : ''}function render(_ctx) {\n    ${option.isBrowser ? 'with (_ctx) {' : ''}\n      const { h } = ChibiVue;\n      return ${genNode(children[0], option)};\n    ${option.isBrowser ? '}' : ''}\n}`\n}\n\n// .\n// .\n// .\n\nconst genNode = (\n  node: TemplateChildNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      return genElement(node, option)\n    case NodeTypes.TEXT:\n      return genText(node)\n    case NodeTypes.INTERPOLATION:\n      return genInterpolation(node, option)\n    default:\n      return ''\n  }\n}\n\nconst genElement = (\n  el: ElementNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `h(\"${el.tag}\", {${el.props\n    .map(prop => genProp(prop, option))\n    .join(', ')}}, [${el.children.map(it => genNode(it, option)).join(', ')}])`\n}\n\nconst genProp = (\n  prop: AttributeNode | DirectiveNode,\n  option: Required<CompilerOptions>,\n): string => {\n  switch (prop.type) {\n    case NodeTypes.ATTRIBUTE:\n      return `${prop.name}: \"${prop.value?.content}\"`\n    case NodeTypes.DIRECTIVE: {\n      switch (prop.name) {\n        case 'on':\n          return `${toHandlerKey(prop.arg)}: ${\n            option.isBrowser ? '' : '_ctx.' // -------------------- 這裡\n          }${prop.exp}`\n        default:\n          // TODO: 其他指令\n          throw new Error(`unexpected directive name. got \"${prop.name}\"`)\n      }\n    }\n    default:\n      throw new Error(`unexpected prop type.`)\n  }\n}\n\n// .\n// .\n// .\n\nconst genInterpolation = (\n  node: InterpolationNode,\n  option: Required<CompilerOptions>,\n): string => {\n  return `${option.isBrowser ? '' : '_ctx.'}${node.content}` // ------------ 這裡\n}\n```\n\n![Compiled SFC template render result](/figures/10-minimum-example/compile-sfc-template/compiled-render-result.png)\n\n看起來編譯成功了．剩下的就是以同樣的方式提取腳本並將其放入預設匯出中．\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler3)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/093-compile-sfc-script.md",
    "content": "# 編譯腳本區塊\n\n## 我們想要做什麼\n\n現在，SFC 的原始腳本部分看起來像這樣：\n\n```ts\nexport default {\n  setup() {},\n}\n```\n\n我想只提取以下部分：\n\n```ts\n  {\n  setup() {},\n}\n```\n\n有什麼方法可以做到這一點嗎？\n\n如果我可以提取這部分，我可以將其與之前生成的渲染函式很好地混合，並按如下方式匯出：\n\n```ts\nconst _sfc_main = {\n  setup() {},\n}\n\nexport default { ..._sfc_main, render }\n```\n\n## 使用外部函式庫\n\n為了實現上述目標，我將使用以下兩個函式庫：\n\n- @babel/parser\n- magic-string\n\n### Babel\n\nhttps://babeljs.io\n\n[What is Babel](https://babeljs.io/docs)\n\n如果你熟悉 JavaScript，你可能聽說過 Babel．\\\nBabel 是一個用於將 JavaScript 轉換為向後相容版本的工具鏈．\\\n簡單來說，它是一個從 JS 到 JS 的編譯器（轉譯器）．\n\n在這種情況下，我將使用 Babel 不僅作為編譯器，還作為解析器．\\\nBabel 有一個內部解析器用於轉換為 AST，因為它扮演編譯器的角色．\n\nAST 代表抽象語法樹，它是 JavaScript 程式碼的表示．\\\n你可以在這裡找到 AST 規範 (https://github.com/estree/estree)。\\\n雖然你可以參考 GitHub md 檔案，但我將簡要解釋 JavaScript 中的 AST．\\\n整個程式由一個 Program AST 節點表示，它包含一個語句陣列（為了清晰起見，使用 TS 介面表示）．\n\n```ts\ninterface Program {\n  body: Statement[]\n}\n```\n\nStatement 表示 JavaScript 中的「語句」，它是語句的集合．\\\n範例包括「變數宣告語句」，「if 語句」，「for 語句」和「區塊語句」．\n\n```ts\ninterface Statement {}\n\ninterface VariableDeclaration extends Statement {\n  /* 省略 */\n}\n\ninterface IfStatement extends Statement {\n  /* 省略 */\n}\n\ninterface ForStatement extends Statement {\n  /* 省略 */\n}\n\ninterface BlockStatement extends Statement {\n  body: Statement[]\n}\n// 還有更多\n```\n\n語句通常在大多數情況下都有一個「表達式」．\\\n表達式是可以分配給變數的東西．\\\n範例包括「物件」，「二元運算」和「函式呼叫」．\n\n```ts\ninterface Expression {}\n\ninterface BinaryExpression extends Expression {\n  operator: '+' | '-' | '*' | '/' // 還有更多，但省略了\n  left: Expression\n  right: Expression\n}\n\ninterface ObjectExpression extends Expression {\n  properties: Property[] // 省略\n}\n\ninterface CallExpression extends Expression {\n  callee: Expression\n  arguments: Expression[]\n}\n\n// 還有更多\n```\n\n如果我們考慮一個 if 語句，它具有以下結構：\n\n```ts\ninterface IfStatement extends Statement {\n  test: Expression // 條件\n  consequent: Statement // 如果條件為真要執行的語句\n  alternate: Statement | null // 如果條件為假要執行的語句\n}\n```\n\n通過這種方式，JavaScript 語法被解析為上述 AST．\\\n我認為對於那些已經為 chibivue 實現了模板編譯器的人來說，這個解釋很容易理解．（這是同樣的事情）\n\n我使用 Babel 的原因有兩個．\\\n首先，這只是因為它很麻煩．\\\n如果你之前實現過解析器，在參考 estree 的同時實現 JS 解析器在技術上可能是可能的．\\\n但是，這非常麻煩，對於「加深對 Vue 的理解」這一目的來說並不是很重要．\\\n另一個原因是官方 Vue 也在這部分使用 Babel．\n\n### magic-string\n\nhttps://github.com/rich-harris/magic-string\n\n還有另一個我想使用的函式庫．\\\n這個函式庫也被官方 Vue 使用．\\\n它是一個使字串操作更容易的函式庫．\n\n```ts\nconst input = 'Hello'\nconst s = new MagicString(input)\n```\n\n你可以像這樣生成一個實例，並使用實例提供的便利方法來操作字串．\\\n以下是一些範例：\n\n```ts\ns.append('!!!') // 追加到末尾\ns.prepend('message: ') // 前置到開頭\ns.overwrite(9, 13, 'こんにちは') // 在範圍內覆寫\n```\n\n沒有必要強制使用它，但我將使用它來與官方 Vue 保持一致．\n\n無論是 Babel 還是 magic-string，你現在都不需要理解實際用法．\\\n我稍後會解釋並對齊實現，所以現在有一個粗略的理解就可以了．\n\n## 重寫腳本的預設匯出\n\n回顧當前目標：\n\n```ts\nexport default {\n  setup() {},\n  // 其他選項\n}\n```\n\n我想將上面的程式碼重寫為：\n\n```ts\nconst _sfc_main = {\n  setup() {},\n  // 其他選項\n}\n\nexport default { ..._sfc_main, render }\n```\n\n換句話說，如果我可以從原始程式碼的匯出語句中提取匯出目標並將其分配給名為 `_sfc_main` 的變數，我將實現目標．\n\n首先，讓我們安裝必要的函式庫．\n\n```sh\npwd # ~\npnpm add @babel/parser magic-string\n```\n\n創建一個名為 \"rewriteDefault.ts\" 的檔案．\n\n```sh\npwd # ~\ntouch packages/compiler-sfc/rewriteDefault.ts\n```\n\n確保函式 \"rewriteDefault\" 可以接收目標原始碼作為 \"input\" 和要綁定的變數名作為 \"as\"．\\\n將轉換後的原始碼作為返回值返回．\n\n`~/packages/compiler-sfc/rewriteDefault.ts`\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // TODO:\n  return ''\n}\n```\n\n首先，讓我們處理匯出宣告不存在的情況．\\\n由於沒有匯出，綁定一個空物件並完成．\n\n```ts\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`\n  }\n\n  // TODO:\n  return ''\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input)\n}\n```\n\n這裡出現了 Babel 解析器和 magic-string．\n\n```ts\nimport { parse } from '@babel/parser'\nimport MagicString from 'magic-string'\n// .\n// .\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  const s = new MagicString(input)\n  const ast = parse(input, {\n    sourceType: 'module',\n  }).program.body\n  // .\n  // .\n}\n```\n\n從這裡開始，我們將基於 Babel 解析器獲得的 JavaScript AST（抽象語法樹）來操作字串 `s`．\\\n雖然有點長，但我將在原始碼的註解中提供額外的解釋．\\\n基本上，我們遍歷 AST 並基於 `type` 屬性編寫條件語句，並使用 `magic-string` 的方法操作字串 `s`．\n\n```ts\nexport function rewriteDefault(input: string, as: string): string {\n  // .\n  // .\n  ast.forEach(node => {\n    // 在預設匯出的情況下\n    if (node.type === 'ExportDefaultDeclaration') {\n      if (node.declaration.type === 'ClassDeclaration') {\n        // 如果是 `export default class Hoge {}`，將其替換為 `class Hoge {}`\n        s.overwrite(node.start!, node.declaration.id.start!, `class `)\n        // 然後，在末尾添加像 `const ${as} = Hoge;` 這樣的程式碼。\n        s.append(`\\nconst ${as} = ${node.declaration.id.name}`)\n      } else {\n        // 對於其他預設匯出，將宣告部分替換為變數宣告。\n        // 例如 1) `export default { setup() {}, }`  ->  `const ${as} = { setup() {}, }`\n        // 例如 2) `export default Hoge`  ->  `const ${as} = Hoge`\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)\n      }\n    }\n\n    // 即使在命名匯出的情況下，宣告中也可能有預設匯出。\n    // 主要有 3 種模式\n    //   1. 在像 `export { default } from \"source\";` 這樣的宣告情況下\n    //   2. 在像 `export { hoge as default }` from 'source' 這樣的宣告情況下\n    //   3. 在像 `export { hoge as default }` 這樣的宣告情況下\n    if (node.type === 'ExportNamedDeclaration') {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === 'ExportSpecifier' &&\n          specifier.exported.type === 'Identifier' &&\n          specifier.exported.name === 'default'\n        ) {\n          // 如果有關鍵字 `from`\n          if (node.source) {\n            if (specifier.local.name === 'default') {\n              // 1. 在像 `export { default } from \"source\";` 這樣的宣告情況下\n              // 在這種情況下，將其提取到匯入語句中並給它一個名稱，然後將其綁定到最終變數。\n              // 例如) `export { default } from \"source\";`  ->  `import { default as __VUE_DEFAULT__ } from 'source'; const ${as} = __VUE_DEFAULT__`\n              const end = specifierEnd(input, specifier.local.end!, node.end!)\n              s.prepend(\n                `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`,\n              )\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`)\n              continue\n            } else {\n              // 2. 在像 `export { hoge as default }` from 'source' 這樣的宣告情況下\n              // 在這種情況下，將所有說明符按原樣重寫為匯入語句，並將作為預設值的變數綁定到最終變數。\n              // 例如) `export { hoge as default } from \"source\";`  ->  `import { hoge } from 'source'; const ${as} = hoge\n              const end = specifierEnd(\n                input,\n                specifier.exported.end!,\n                node.end!,\n              )\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              )\n\n              // 3. 在像 `export { hoge as default }` 這樣的宣告情況下\n              // 在這種情況下，簡單地將其綁定到最終變數。\n              s.overwrite(specifier.start!, end, ``)\n              s.append(`\\nconst ${as} = ${specifier.local.name}`)\n              continue\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!)\n          s.overwrite(specifier.start!, end, ``)\n          s.append(`\\nconst ${as} = ${specifier.local.name}`)\n        }\n      }\n    }\n  })\n  return s.toString()\n}\n\n// 計算宣告語句的結束\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false\n  let oldEnd = end\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++\n    } else if (input.charAt(end) === ',') {\n      end++\n      hasCommas = true\n      break\n    } else if (input.charAt(end) === '}') {\n      break\n    }\n  }\n  return hasCommas ? end : oldEnd\n}\n```\n\n現在你可以重寫預設匯出了．\\\n讓我們嘗試在外掛程式中使用它．\n\n```ts\nimport type { Plugin } from 'vite'\nimport { createFilter } from 'vite'\nimport { parse, rewriteDefault } from '../../compiler-sfc'\nimport { compile } from '../../compiler-dom'\n\nexport default function vitePluginChibivue(): Plugin {\n  const filter = createFilter(/\\.vue$/)\n\n  return {\n    name: 'vite:chibivue',\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n\n      const { descriptor } = parse(code, { filename: id })\n\n      // --------------------------- 從這裡\n      const SFC_MAIN = '_sfc_main'\n      const scriptCode = rewriteDefault(\n        descriptor.script?.content ?? '',\n        SFC_MAIN,\n      )\n      outputs.push(scriptCode)\n      // --------------------------- 到這裡\n\n      const templateCode = compile(descriptor.template?.content ?? '', {\n        isBrowser: false,\n      })\n      outputs.push(templateCode)\n\n      outputs.push('\\n')\n      outputs.push(`export default { ...${SFC_MAIN}, render }`) // 這裡\n\n      return { code: outputs.join('\\n') }\n    },\n  }\n}\n```\n\n在此之前，讓我們做一個小修改．\n\n`~/packages/runtime-core/component.ts`\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n  // 將組件的渲染選項添加到實例\n  const { render } = component\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n}\n```\n\n現在你應該能夠渲染了！！！\n\n![Rendered SFC script result](/figures/10-minimum-example/compile-sfc-script/render-sfc-result.png)\n\n樣式沒有應用，因為不支援，但現在你可以渲染組件了．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/094-compile-sfc-style.md",
    "content": "# 編譯樣式區塊\n\n## 虛擬模組\n\n讓我們也支援樣式．\\\n在 Vite 中，你可以通過使用 `.css` 副檔名來匯入 CSS 檔案．\n\n```js\nimport 'app.css'\n```\n\n我們將通過使用 Vite 的虛擬模組來實現這一點．\\\n虛擬模組允許你將不存在的檔案保存在記憶體中，就像它們存在一樣．\\\n你可以使用 `load` 和 `resolveId` 選項來實現虛擬模組．\n\n```ts\nexport default function myPlugin() {\n  const virtualModuleId = 'virtual:my-module'\n\n  return {\n    name: 'my-plugin', // 必需，在警告和錯誤中顯示\n    resolveId(id) {\n      if (id === virtualModuleId) {\n        return virtualModuleId\n      }\n    },\n    load(id) {\n      if (id === virtualModuleId) {\n        return `export const msg = \"from virtual module\"`\n      }\n    },\n  }\n}\n```\n\n使用這種機制，我們將把 SFC 的樣式區塊作為虛擬 CSS 檔案載入．\\\n如前所述，在 Vite 中，匯入帶有 `.css` 副檔名的檔案就足夠了，所以我們將考慮創建一個名為 `${SFC 檔案名}.css` 的虛擬模組．\n\n## 實現包含 SFC 樣式區塊內容的虛擬模組\n\n對於這個範例，讓我們考慮一個名為 \"App.vue\" 的檔案，並為其樣式部分實現一個名為 \"App.vue.css\" 的虛擬模組．\\\n過程很簡單：當載入名為 `**.vue.css` 的檔案時，我們將使用 `fs.readFileSync` 從不帶 `.css` 的檔案路徑（即原始 Vue 檔案）檢索 SFC，解析它以提取樣式標籤的內容，並將該內容作為程式碼返回．\n\n```ts\nexport default function vitePluginChibivue(): Plugin {\n  //  ,\n  //  ,\n  //  ,\n  return {\n    //  ,\n    //  ,\n    //  ,\n    resolveId(id) {\n      // 這個 ID 是一個不存在的路徑，但我們在 load 中虛擬處理它，所以我們返回 ID 以表明它可以被載入\n      if (id.match(/\\.vue\\.css$/)) return id\n\n      // 對於這裡沒有返回的 ID，如果檔案實際存在，檔案將被解析，如果不存在，將拋出錯誤\n    },\n    load(id) {\n      // 處理載入 .vue.css 時（當宣告 import 並載入時）\n      if (id.match(/\\.vue\\.css$/)) {\n        const filename = id.replace(/\\.css$/, '')\n        const content = fs.readFileSync(filename, 'utf-8') // 正常檢索 SFC 檔案\n        const { descriptor } = parse(content, { filename }) // 解析 SFC\n\n        // 連接內容並將其作為結果返回\n        const styles = descriptor.styles.map(it => it.content).join('\\n')\n        return { code: styles }\n      }\n    },\n\n    transform(code, id) {\n      if (!filter(id)) return\n\n      const outputs = []\n      outputs.push(\"import * as ChibiVue from 'chibivue'\")\n      outputs.push(`import '${id}.css'`) // 為 ${id}.css 宣告匯入語句\n      //  ,\n      //  ,\n      //  ,\n    },\n  }\n}\n```\n\n現在，讓我們在瀏覽器中檢查．\n\n![Virtual CSS module request in the browser](/figures/10-minimum-example/compile-sfc-style/load-virtual-css-module.png)\n\n看起來樣式被正確應用了．\n\n在瀏覽器中，你可以看到 CSS 被匯入，並且虛擬生成了一個 `.vue.css` 檔案．\n\n![Loaded CSS module in the browser](/figures/10-minimum-example/compile-sfc-style/loaded-css-in-browser.png)\n![Generated Vue CSS module](/figures/10-minimum-example/compile-sfc-style/generated-vue-css-module.png)\n\n現在你可以使用 SFC 了！\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/10_minimum_example/070_sfc_compiler4)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/10-minimum-example/100-break.md",
    "content": "# 休息一下\n\n## 最小範例部分結束了！\n\n在開始時，我提到這本書分為幾個部分，第一部分「最小範例部分」現在已經完成．做得好！\\\n如果你對虛擬 DOM 或補丁渲染感興趣，你可以繼續到基礎虛擬 DOM 部分．\\\n如果你想進一步擴展組件，有基礎組件部分．如果你對模板中更豐富的表達式（如指令）感興趣，你可以探索基礎模板編譯器部分．\\\n如果你對 script setup 或編譯器巨集感興趣，你可以繼續到基礎 SFC 編譯器部分．（當然，如果你願意，你可以全部做！！）\\\n最重要的是，「最小範例部分」也是一個值得尊敬的部分，所以如果你覺得，「我不需要了解得太深入，但我想得到一個大致的想法」，那麼你到這裡就足夠了．\n\n## 到目前為止我們取得了什麼成就？\n\n最後，讓我們反思一下我們在最小範例部分做了什麼以及取得了什麼成就．\n\n## 我們現在知道我們在看什麼以及它屬於哪裡\n\n首先，通過名為 createApp 的初始開發者介面，我們了解了（web 應用）開發者和 Vue 世界是如何連接的．\\\n具體來說，從我們在開始時進行的重構開始，你現在應該了解 Vue 目錄結構的基礎，它的相依性以及開發者正在工作的地方．\\\n讓我們比較當前目錄和 vuejs/core 的目錄．\n\nchibivue\n![Minimum example implementation artifacts](/figures/10-minimum-example/break/minimum-example-artifacts.png)\n\n\\*原始程式碼太大，無法在截圖中顯示，所以省略了．\n\nhttps://github.com/vuejs/core\n\n儘管它很小，你現在應該能夠在某種程度上閱讀和理解每個檔案的角色和內容．\\\n我希望你也會挑戰自己閱讀我們這次沒有涵蓋的部分的原始碼．（你應該能夠一點一點地閱讀它！）\n\n## 我們現在知道宣告式 UI 是如何實現的\n\n通過 h 函式的實現，我們了解了宣告式 UI 是如何實現的．\n\n```ts\n// 在內部，它生成一個像 {tag, props, children} 這樣的物件，並基於它執行 DOM 操作\nh('div', { id: 'my-app' }, [\n  h('p', {}, ['Hello!']),\n  h(\n    'button',\n    {\n      onClick: () => {\n        alert('hello')\n      },\n    },\n    ['Click me!'],\n  ),\n])\n```\n\n這是虛擬 DOM 之類的東西首次出現的地方．\n\n## 我們現在知道響應式系統是什麼以及如何動態更新螢幕\n\n我們了解了 Vue 獨特功能響應式系統的實現，它是如何工作的以及它實際上是什麼．\n\n```ts\nconst targetMap = new WeakMap<any, KeyToDepMap>()\n\nfunction reactive<T extends object>(target: T): T {\n  const proxy = new Proxy(target, {\n    get(target: object, key: string | symbol, receiver: object) {\n      track(target, key)\n      return Reflect.get(target, key, receiver)\n    },\n\n    set(\n      target: object,\n      key: string | symbol,\n      value: unknown,\n      receiver: object,\n    ) {\n      Reflect.set(target, key, value, receiver)\n      trigger(target, key)\n      return true\n    },\n  })\n}\n```\n\n```ts\nconst component = {\n  setup() {\n    const state = reactive({ count: 0 }) // 創建代理\n\n    const increment = () => {\n      state.count++ // 觸發\n    }\n\n    ;() => {\n      return h('p', {}, `${state.count}`) // 追蹤\n    }\n  },\n}\n```\n\n## 我們現在知道虛擬 DOM 是什麼，為什麼它有益，以及如何實現它\n\n作為使用 h 函式渲染的改進，我們通過比較了解了使用虛擬 DOM 的高效渲染方法．\n\n```ts\n// 虛擬 DOM 的介面\nexport interface VNode<HostNode = any> {\n  type: string | typeof Text | object\n  props: VNodeProps | null\n  children: VNodeNormalizedChildren\n  el: HostNode | undefined\n}\n\n// 首先，呼叫渲染函式\nconst render: RootRenderFunction = (rootComponent, container) => {\n  const vnode = createVNode(rootComponent, {}, [])\n  // 第一次，n1 是 null。在這種情況下，每個過程執行 mount\n  patch(null, vnode, container)\n}\n\nconst patch = (n1: VNode | null, n2: VNode, container: RendererElement) => {\n  const { type } = n2\n  if (type === Text) {\n    processText(n1, n2, container)\n  } else if (typeof type === 'string') {\n    processElement(n1, n2, container)\n  } else if (typeof type === 'object') {\n    processComponent(n1, n2, container)\n  } else {\n    // do nothing\n  }\n}\n\n// 從第二次開始，將前一個 VNode 和當前 VNode 傳遞給 patch 函式以更新差異\nconst nextVNode = component.render()\npatch(prevVNode, nextVNode)\n```\n\n我了解了組件的結構以及組件之間的交互是如何實現的．\n\n```ts\nexport interface ComponentInternalInstance {\n  type: Component\n\n  vnode: VNode\n  subTree: VNode\n  next: VNode | null\n  effect: ReactiveEffect\n  render: InternalRenderFunction\n  update: () => void\n\n  propsOptions: Props\n  props: Data\n  emit: (event: string, ...args: any[]) => void\n\n  isMounted: boolean\n}\n```\n\n```ts\nconst MyComponent = {\n  props: { someMessage: { type: String } },\n\n  setup(props: any, { emit }: any) {\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`someMessage: ${props.someMessage}`]),\n        h('button', { onClick: () => emit('click:change-message') }, [\n          'change message',\n        ]),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'hello' })\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    return () =>\n      h('div', { id: 'my-app' }, [\n        h(\n          MyComponent,\n          {\n            'some-message': state.message,\n            'onClick:change-message': changeMessage,\n          },\n          [],\n        ),\n      ])\n  },\n})\n```\n\n我了解了編譯器是什麼以及模板功能是如何實現的．\n\n通過了解編譯器是什麼並實現模板編譯器，我獲得了如何實現更原始的類似 HTML 的實現以及如何實現 Vue 特定功能（如 Mustache 語法）的理解．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = (e: InputEvent) => {\n      state.input = (e.target as HTMLInputElement)?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n\n  template: `\n    <div class=\"container\" style=\"text-align: center\">\n      <h2>{{ state.message }}</h2>\n      <img\n        width=\"150px\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n        alt=\"Vue.js Logo\"\n      />\n      <p><b>chibivue</b> is the minimal Vue.js</p>\n\n      <button @click=\"changeMessage\"> click me! </button>\n\n      <br />\n\n      <label>\n        Input Data\n        <input @input=\"handleInput\" />\n      </label>\n\n      <p>input value: {{ state.input }}</p>\n\n      <style>\n        .container {\n          height: 100vh;\n          padding: 16px;\n          background-color: #becdbe;\n          color: #2c3e50;\n        }\n      </style>\n    </div>\n  `,\n})\n```\n\n我了解了如何通過 Vite 外掛程式實現 SFC 編譯器．\n\n通過實現模板編譯器並通過 Vite 外掛程式利用它，我獲得了如何實現將腳本，模板和樣式組合到一個檔案中的原始檔案格式的理解．\\\n我還了解了 Vite 外掛程式可以做什麼，以及 transform 和虛擬模組．\n\n```vue\n<script>\nimport { reactive } from 'chibivue'\n\nexport default {\n  setup() {\n    const state = reactive({ message: 'Hello, chibivue!', input: '' })\n\n    const changeMessage = () => {\n      state.message += '!'\n    }\n\n    const handleInput = e => {\n      state.input = e.target?.value ?? ''\n    }\n\n    return { state, changeMessage, handleInput }\n  },\n}\n</script>\n\n<template>\n  <div class=\"container\" style=\"text-align: center\">\n    <h2>{{ state.message }}</h2>\n    <img\n      width=\"150px\"\n      src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1200px-Vue.js_Logo_2.svg.png\"\n      alt=\"Vue.js Logo\"\n    />\n    <p><b>chibivue</b> is the minimal Vue.js</p>\n\n    <button @click=\"changeMessage\">click me!</button>\n\n    <br />\n\n    <label>\n      Input Data\n      <input @input=\"handleInput\" />\n    </label>\n\n    <p>input value: {{ state.input }}</p>\n  </div>\n</template>\n\n<style>\n.container {\n  height: 100vh;\n  padding: 16px;\n  background-color: #becdbe;\n  color: #2c3e50;\n}\n</style>\n```\n\n## 關於未來\n\n從現在開始，為了使其更實用，我們將在每個部分中更詳細地介紹．\\\n我將稍微解釋一下每個部分要做什麼以及如何進行（政策）．\n\n### 要做什麼\n\n從這裡開始，它將分為 5 個部分 + 1 個附錄．\n\n- 基礎虛擬 DOM 部分\n  - 排程器的實現\n  - 不支援的補丁的實現（主要與屬性相關）\n  - Fragment 的支援\n- 基礎響應式系統部分\n  - ref API\n  - computed API\n  - watch API\n- 基礎組件系統部分\n  - provide/inject\n  - 生命週期鉤子\n- 基礎模板編譯器部分\n  - v-on\n  - v-bind\n  - v-for\n  - v-model\n- 基礎 SFC 編譯器部分\n  - SFC 的基礎\n  - script setup\n  - 編譯器巨集\n- Web 應用程式要點部分（附錄）\n\n這部分是附錄．\\\n在這部分中，我們將實現在 web 開發中經常與 Vue 一起使用的函式庫．\n\n- store\n- route\n\n我們將涵蓋上述兩個，但請隨意實現其他想到的東西！\n\n### 政策\n\n在最小範例部分，我們相當詳細地解釋了實現步驟．\\\n到現在，如果你已經實現了它，你應該能夠閱讀原始 Vue 的原始碼．\\\n因此，從現在開始，解釋將保持粗略的政策，你將在閱讀原始程式碼或自己思考的同時實現實際程式碼．\\\n（不-不，這不是我變得懶惰而不願意詳細寫作或類似的事情！）\\\n嗯，按照書中所說的實現是有趣的，但一旦它開始成形，自己做更有趣，並且會導致更深的理解．\\\n從這裡開始，請將這本書視為一種指導方針，主要內容在原始 Vue 原始碼中！\n"
  },
  {
    "path": "book/online-book/src/zh-tw/20-basic-virtual-dom/010-patch-keyed-children.md",
    "content": "# key 屬性和補丁渲染（基礎虛擬DOM章節開始）\n\n## 關鍵錯誤\n\n實際上，當前 chibivue 的補丁渲染中存在一個關鍵錯誤．  \n在實現補丁渲染時，\n\n> 關於 patchChildren，需要透過添加 key 等屬性來處理動態大小的子元素。\n\n你還記得說過這句話嗎？\n\n讓我們看看實際會發生什麼樣的問題．\n在當前的實現中，patchChildren 是這樣實現的：\n\n```ts\nconst patchChildren = (n1: VNode, n2: VNode, container: RendererElement) => {\n  const c1 = n1.children as VNode[]\n  const c2 = n2.children as VNode[]\n\n  for (let i = 0; i < c2.length; i++) {\n    const child = (c2[i] = normalizeVNode(c2[i]))\n    patch(c1[i], child, container)\n  }\n}\n```\n\n這是基於 c2（即下一個 vnode）的長度進行循環的．\n換句話說，它基本上只在 c1 和 c2 相同時才能正常工作．\n\n![Index-based patch with equal lengths](/figures/20-basic-virtual-dom/patch-keyed-children/same-length-index-patch.svg)\n\n例如，讓我們考慮元素被刪除的情況．\n由於補丁循環基於 c2，第四個元素的補丁將不會被執行．\n\n![Deleted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/deleted-child-bug.svg)\n\n當變成這樣時，第一到第三個元素只是簡單地更新，而第四個元素仍然是來自 c1 的未被刪除的元素．\n\n讓我們看看實際效果．\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ list: ['a', 'b', 'c', 'd'] })\n    const updateList = () => {\n      state.list = ['e', 'f', 'g']\n    }\n\n    return () =>\n      h('div', { id: 'app' }, [\n        h(\n          'ul',\n          {},\n          state.list.map(item => h('li', {}, [item])),\n        ),\n        h('button', { onClick: updateList }, ['update']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n當你點擊更新按鈕時，應該是這樣的：\n\n![Stale child bug rendered in the browser](/figures/20-basic-virtual-dom/patch-keyed-children/stale-child-bug-result.png)\n\n雖然列表應該已經更新為 `[\"e\", \"f\", \"g\"]`，但 \"d\" 仍然存在．\n\n實際上，問題不僅僅是這個．讓我們考慮元素被插入的情況．\n目前，由於循環基於 c2，它變成這樣：\n\n![Inserted child bug in index-based patching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-index-bug.svg)\n\n然而，實際上，\"新元素\"被插入了，比較應該在 c1 和 c2 的每個 li 1，li 2，li 3 和 li 4 之間進行．\n\n![Inserted child handled with keyed matching](/figures/20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg)\n\n這兩個問題的共同點是\"無法確定 c1 和 c2 中需要被視為相同的節點\"．  \n為了解決這個問題，需要為元素分配一個 key，並基於該 key 進行補丁．  \n現在，讓我們看看 Vue 文件中對 key 屬性的解釋．\n\n> 特殊屬性 key 主要用作 Vue 虛擬DOM演算法的提示，在比較新舊節點列表時識別 VNode。\n\nhttps://v3-migration.vuejs.org/breaking-changes/key-attribute.html\n\n正如預期的那樣，對吧？你可能聽過\"不要使用索引作為 v-for 的 key\"的建議，但在這一點上，key 被隱式設置為索引，這就是為什麼會出現上述問題．（循環基於 c2 的長度，並基於該索引進行補丁）\n\n## 基於 key 屬性的補丁\n\n實現這些功能的函式是 `patchKeyedChildren`．（讓我們在原始 Vue 中搜尋它．）\n\n方法是首先為新節點生成 key 和索引的映射．\n\n```ts\nlet i = 0\nconst l2 = c2.length\nconst e1 = c1.length - 1 // prev node 的結束索引\nconst e2 = l2 - 1 // next node 的結束索引\n\nconst s1 = i // prev node 的開始索引\nconst s2 = i // next node 的開始索引\n\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = s2; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n```\n\n在原始 Vue 中，這個 `patchKeyedChildren` 分為五個部分：\n\n1. sync from start\n2. sync from end\n3. common sequence + mount\n4. common sequence + unmount\n5. unknown sequence\n\n然而，最後一部分 `unknown sequence` 是唯一在功能上必需的，所以我們將從閱讀和實現該部分開始．\n\n首先，忘記移動元素，基於 key 對 VNode 進行補丁．\n使用我們之前創建的 `keyToNewIndexMap`，計算 n1 和 n2 的配對並對它們進行補丁．\n此時，如果有新元素需要掛載或需要卸載，也要執行這些操作．\n\n大致來說，它看起來像這樣 ↓（我跳過了很多細節．請閱讀 vuejs/core 的 renderer.ts 了解更多詳細資訊．）\n\n```ts\nconst toBePatched = e2 + 1\nconst newIndexToOldIndexMap = new Array(toBePatched) // 新索引到舊索引的映射\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// 基於 e1（舊長度）的循環\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // 如果在新的中不存在，卸載它\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // 形成映射\n    patch(prevChild, c2[newIndex] as VNode, container) // 補丁\n  }\n}\n\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  if (newIndexToOldIndexMap[i] === 0) {\n    // 如果映射不存在（保持初始值），意味著它需要新掛載。（實際上，它存在但不在舊的中）\n    patch(null, nextChild, container, anchor)\n  }\n}\n```\n\n## 移動元素\n\n### 方法\n\n#### Node.insertBefore\n\n目前，我們只基於 key 匹配更新每個元素，所以如果元素被移動，我們需要編寫程式碼將其移動到所需位置．\n\n首先，讓我們談談如何移動元素．我們在 `nodeOps` 的 `insert` 函式中指定錨點．錨點，顧名思義，是一個錨點，如果你查看在 runtime-dom 中實現的 `insert` 方法，你可以看到它是用 `insertBefore` 方法實現的．\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // .\n  // .\n  // .\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null)\n  },\n}\n```\n\n透過將節點作為第二個參數傳遞給此方法，節點將被插入到該節點之前．  \nhttps://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore\n\n我們使用這個方法來實際移動 DOM．\n\n#### LIS（最長遞增子序列）\n\n現在，讓我們談談如何編寫移動演算法．這部分稍微複雜一些．  \n與執行 JavaScript 相比，DOM 操作的成本要高得多，所以我們希望盡可能減少不必要的移動次數．  \n這就是我們使用\"最長遞增子序列\"（LIS）演算法的地方．  \n這個演算法在陣列中找到最長的遞增子序列．  \n遞增子序列是元素按遞增順序排列的子序列．  \n例如，給定以下陣列：\n\n```\n[2, 4, 1, 7, 5, 6]\n```\n\n有幾個遞增子序列：\n\n```\n[2, 4]\n[2, 5]\n.\n.\n[2, 4, 7]\n[2, 4, 5]\n.\n.\n[2, 4, 5, 6]\n.\n.\n[1, 7]\n.\n.\n[1, 5, 6]\n```\n\n這些是元素遞增的子序列．最長的一個是\"最長遞增子序列\"．  \n在這種情況下，`[2, 4, 5, 6]` 是最長遞增子序列．在 Vue 中，對應於 2，4，5 和 6 的索引被視為結果陣列（即 `[0, 1, 4, 5]`）．\n\n順便說一下，這裡是一個示例函式：\n\n```ts\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice()\n  const result = [0]\n  let i, j, u, v, c\n  const len = arr.length\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i]\n    if (arrI !== 0) {\n      j = result[result.length - 1]\n      if (arr[j] < arrI) {\n        p[i] = j\n        result.push(i)\n        continue\n      }\n      u = 0\n      v = result.length - 1\n      while (u < v) {\n        c = (u + v) >> 1\n        if (arr[result[c]] < arrI) {\n          u = c + 1\n        } else {\n          v = c\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1]\n        }\n        result[u] = i\n      }\n    }\n  }\n  u = result.length\n  v = result[u - 1]\n  while (u-- > 0) {\n    result[u] = v\n    v = p[v]\n  }\n  return result\n}\n```\n\n我們將使用這個函式從 `newIndexToOldIndexMap` 計算最長遞增子序列，並基於此，我們將使用 `insertBefore` 插入其他節點．\n\n### 具體示例\n\n這裡有一個具體的示例來讓它更容易理解．\n\n讓我們考慮兩個 VNode 陣列，`c1` 和 `c2`．`c1` 表示更新前的狀態，`c2` 表示更新後的狀態．每個 VNode 都有一個 `key` 屬性（實際上，它包含更多資訊）．\n\n```js\nc1 = [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }]\nc2 = [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }]\n```\n\n首先，讓我們基於 `c2` 生成 `keyToNewIndexMap`（key 到 `c2` 中索引的映射）．\n※ 這是之前介紹的程式碼．\n\n```ts\nconst keyToNewIndexMap: Map<string | number | symbol, number> = new Map()\nfor (i = 0; i <= e2; i++) {\n  const nextChild = (c2[i] = normalizeVNode(c2[i]))\n  if (nextChild.key != null) {\n    keyToNewIndexMap.set(nextChild.key, i)\n  }\n}\n\n// keyToNewIndexMap = { a: 0, b: 1, d: 2, c: 3 }\n```\n\n接下來，讓我們生成 `newIndexToOldIndexMap`．\n※ 這是之前介紹的程式碼．\n\n```ts\n// 初始化\n\nconst toBePatched = c2.length\nconst newIndexToOldIndexMap = new Array(toBePatched) // 新索引到舊索引的映射\nfor (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0\n\n// newIndexToOldIndexMap = [0, 0, 0, 0]\n```\n\n```ts\n// 執行補丁並生成用於移動的 newIndexToOldIndexMap\n\n// 基於 e1（舊長度）的循環\nfor (i = 0; i <= e1; i++) {\n  const prevChild = c1[i]\n  newIndex = keyToNewIndexMap.get(prevChild.key)\n  if (newIndex === undefined) {\n    // 如果在新陣列中不存在，卸載它\n    unmount(prevChild)\n  } else {\n    newIndexToOldIndexMap[newIndex] = i + 1 // 形成映射\n    patch(prevChild, c2[newIndex] as VNode, container) // 執行補丁\n  }\n}\n\n// newIndexToOldIndexMap = [1, 2, 4, 3]\n```\n\n然後，從獲得的 `newIndexToOldIndexMap` 中獲取最長遞增子序列（新實現從這裡開始）．\n\n```ts\nconst increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)\n// increasingNewIndexSequence  = [0, 1, 3]\n```\n\n```ts\nj = increasingNewIndexSequence.length - 1\nfor (i = toBePatched - 1; i >= 0; i--) {\n  const nextIndex = i\n  const nextChild = c2[nextIndex] as VNode\n  const anchor =\n    nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor // ※ parentAnchor 暫時可以認為是參數中接收到的 anchor。\n\n  if (newIndexToOldIndexMap[i] === 0) {\n    // newIndexToOldIndexMap 的初始值是 0，所以如果是 0，則判斷為不存在到舊元素的映射，即是新元素。\n    patch(null, nextChild, container, anchor)\n  } else {\n    // 如果 i 和 increasingNewIndexSequence[j] 不匹配，則進行移動\n    if (j < 0 || i !== increasingNewIndexSequence[j]) {\n      move(nextChild, container, anchor)\n    } else {\n      j--\n    }\n  }\n}\n```\n\n### 讓我們實現它．\n\n現在我們已經詳細解釋了方法，讓我們實際實現 `patchKeyedChildren`．以下是步驟總結：\n\n1. 準備用於桶接力的 `anchor`（用於在 `move` 中插入）．\n2. 基於 `c2` 創建 key 和索引的映射．\n3. 基於 key 映射創建 `c2` 和 `c1` 中索引的映射．\n   在這個階段，在基於 `c1` 和基於 `c2` 的循環中執行補丁過程（不包括 `move`）．\n4. 基於步驟 3 中獲得的映射找到最長遞增子序列．\n5. 基於步驟 4 中獲得的子序列和 `c2` 執行 `move`．\n\n你可以參考原始 Vue 實現或 chibivue 實現作為指導．（我建議在跟隨的同時閱讀原始 Vue 實現．）\n"
  },
  {
    "path": "book/online-book/src/zh-tw/20-basic-virtual-dom/020-bit-flags.md",
    "content": "# 使用位元表示 VNode\n\n## 使用位元表示 VNode 的類型\n\nVNode 有各種類型．例如，目前實現的包括：\n\n- 組件節點\n- 元素節點\n- 文字節點\n- 子元素是否為文字\n- 子元素是否為陣列\n\n在未來，將實現更多類型的 VNode．例如，slot，keep-alive，suspense，teleport 等．\n\n目前，分支是使用 `type === Text`，`typeof type === \"string\"`，`typeof type === \"object\"` 等條件進行的．\n\n逐一檢查這些條件是低效的，所以讓我們嘗試按照原始實現使用位元來表示它們．在 Vue 中，這些位元被稱為\"ShapeFlags\"．顧名思義，它們表示 VNode 的形狀．（嚴格來說，在 Vue 中，ShapeFlags 和 Text，Fragment 等符號用於確定 VNode 的類型．）\nhttps://github.com/vuejs/core/blob/main/packages/shared/src/shapeFlags.ts\n\n位元標誌是指將數字的每一位元視為特定標誌．\n\n讓我們以下面的 VNode 為例：\n\n```ts\nconst vnode = {\n  type: 'div',\n  children: [\n    { type: 'p', children: ['hello'] },\n    { type: 'p', children: ['hello'] },\n  ],\n}\n```\n\n![ShapeFlags pack VNode shape into bits](/figures/20-basic-virtual-dom/bit-flags/shape-flag-overview.svg)\n\n首先，標誌的初始值是 0．（為了簡單起見，這個解釋使用 8 位元．）\n\n```ts\nlet shape = 0b0000_0000\n```\n\n現在，這個 VNode 是一個元素並且有一個子元素陣列，所以設置 ELEMENT 標誌和 ARRAY_CHILDREN 標誌．\n\n```ts\nshape = shape | ShapeFlags.ELEMENT | ELEMENT.ARRAY_CHILDREN // 0x00010001\n```\n\n透過這種方式，我們可以使用一個名為\"shape\"的數字來表示這個 VNode 是一個元素並且有一個子元素陣列的資訊．我們可以透過在渲染器或程式碼的其他部分的分支中使用它來高效地管理 VNode 的類型．\n\n```ts\nif (vnode.shape & ShapeFlags.ELEMENT) {\n  // vnode 是元素時的處理\n}\n```\n\n由於這次我們沒有實現所有的 ShapeFlags，請嘗試實現以下內容作為練習：\n\n```ts\nexport const enum ShapeFlags {\n  ELEMENT = 1,\n  COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n}\n```\n\n你需要做的是：\n\n- 在 shared/shapeFlags.ts 中定義標誌\n- 在 runtime-core/vnode.ts 中定義 shape\n  ```ts\n  export interface VNode<HostNode = any> {\n    shapeFlag: number\n  }\n  ```\n  添加這個並在 createVNode 等函式中計算標誌．\n- 在渲染器中基於 shape 實現分支邏輯．\n\n這就是本章的解釋．讓我們開始實現吧！\n"
  },
  {
    "path": "book/online-book/src/zh-tw/20-basic-virtual-dom/030-scheduler.md",
    "content": "# 調度器\n\n## 調度 Effect\n\n首先，看看這段程式碼：\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      message: 'Hello World',\n    })\n    const updateState = () => {\n      state.message = 'Hello ChibiVue!'\n      state.message = 'Hello ChibiVue!!'\n    }\n\n    return () => {\n      console.log('😎 rendered!')\n\n      return h('div', { id: 'app' }, [\n        h('p', {}, [`message: ${state.message}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n當按鈕被點擊時，`state.message` 上的 `set` 函式被呼叫兩次，所以自然地，`trigger` 函式也會被執行兩次．這意味著虛擬 DOM 將被計算兩次，補丁也會被執行兩次．\n\n![Effect result before scheduler batching](/figures/20-basic-virtual-dom/scheduler/non-scheduled-effect.png)\n\n然而，實際上，補丁只需要執行一次，在第二次觸發時．  \n因此，我們將實現一個調度器．調度器負責管理任務的執行順序和控制．Vue 調度器的作用之一是在佇列中管理響應式 effect，並在可能的情況下合併它們．\n\n## 使用佇列管理進行調度\n\n具體來說，我們將有一個佇列來管理作業．每個作業都有一個 ID，當新作業入佇列時，如果佇列中已經有相同 ID 的作業，它將被覆蓋．\n\n```ts\nexport interface SchedulerJob extends Function {\n  id?: number\n}\n\nconst queue: SchedulerJob[] = []\n\nexport function queueJob(job: SchedulerJob) {\n  if (\n    !queue.length ||\n    !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)\n  ) {\n    if (job.id == null) {\n      queue.push(job)\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job)\n    }\n    queueFlush()\n  }\n}\n```\n\n至於作業 ID，在這種情況下，我們希望按組件分組，所以我們將為每個組件分配一個唯一識別符（UID）並將其用作作業 ID．  \nUID 只是透過遞增計數器獲得的識別符．\n\n## ReactiveEffect 和調度器\n\n目前，ReactiveEffect 具有以下介面（部分省略）：\n\n```ts\nclass ReactiveEffect {\n  public fn: () => T,\n\n  run() {}\n}\n```\n\n隨著調度器的實現，讓我們做一個小改變．  \n目前，我們將函式註冊到 `fn` 作為 effect，但這次，讓我們將其分為\"主動執行的 effect\"和\"被動執行的 effect\"．  \n響應式 effect 可以由設置 effect 的一方主動執行，也可以在被添加到依賴項（`dep`）後被某些外部操作觸發而被動執行．  \n對於後一種類型的 effect，它被添加到多個 `depsMap` 並由多個源觸發，需要調度（另一方面，如果它被明確主動呼叫，則不需要這樣的調度）．\n\n讓我們考慮一個具體的例子．在渲染器的 `setupRenderEffect` 函式中，你可能有以下實現：\n\n```ts\nconst effect = (instance.effect = new ReactiveEffect(() => componentUpdateFn))\nconst update = (instance.update = () => effect.run())\nupdate()\n```\n\n這裡創建的 `effect`，它是一個 `reactiveEffect`，稍後在執行 `setup` 函式時將被響應式物件追蹤．這顯然需要調度的實現（因為它將從各個地方被觸發）．  \n然而，關於這裡呼叫的 `update()` 函式，它應該簡單地執行 effect，所以不需要調度．  \n你可能會想，\"那我們不能直接呼叫 `componentUpdateFn` 嗎？\"但請記住 `run` 函式的實現．簡單地呼叫 `componentUpdateFn` 不會設置 `activeEffect`．  \n所以，讓我們分離\"主動執行的 effect\"和\"被動執行的 effect（需要調度的 effect）\"．\n\n作為本章的最終介面，它將如下所示：\n\n```ts\n// ReactiveEffect 的第一個參數是主動執行的 effect，第二個參數是被動執行的 effect\nconst effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () =>\n  queueJob(update),\n))\nconst update: SchedulerJob = (instance.update = () => effect.run())\nupdate.id = instance.uid\nupdate()\n```\n\n在實現方面，除了 `fn` 之外，`ReactiveEffect` 將有一個 `scheduler` 函式，在 `triggerEffect` 函式中，如果存在調度器，將首先執行調度器．\n\n```ts\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport class ReactiveEffect<T = any> {\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null\n  );\n}\n```\n\n```ts\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler()\n  } else {\n    effect.run() // 如果沒有調度器，正常執行 effect\n  }\n}\n```\n\n---\n\n現在，讓我們在閱讀原始碼的同時實現佇列管理調度和 effect 分類！\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/040_scheduler)\n\n## 我們需要 nextTick\n\n如果你在實現調度器時閱讀了原始碼，你可能已經注意到\"nextTick\"的出現並想知道它是否在這裡使用．首先，讓我們談談這次我們想要實現的任務．請看這段程式碼：\n\n```ts\nimport { createApp, h, reactive } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = () => {\n      state.count++\n\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n嘗試點擊這個按鈕並查看控制台．\n\n![Old DOM state before nextTick](/figures/20-basic-virtual-dom/scheduler/old-state-dom.png)\n\n即使我們在更新 `state.count` 後輸出到控制台，資訊也是過時的．這是因為當狀態更新時，DOM 不會立即更新，在控制台輸出時，DOM 仍處於舊狀態．\n\n這就是\"nextTick\"發揮作用的地方．\n\nhttps://vuejs.org/api/general.html#nexttick\n\n\"nextTick\"是調度器的一個 API，它允許你等待直到調度器應用 DOM 更改．\"nextTick\"的實現非常簡單．它只是保持調度器中正在刷新的作業（promise）並將其連接到\"then\"．\n\n```ts\nexport function nextTick<T = void>(\n  this: T,\n  fn?: (this: T) => void,\n): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise\n  return fn ? p.then(this ? fn.bind(this) : fn) : p\n}\n```\n\n當作業完成時（promise 被解析），傳遞給\"nextTick\"的回呼被執行．（如果佇列中沒有作業，它連接到\"resolvedPromise\"的\"then\"）自然地，\"nextTick\"本身也返回一個 Promise，所以作為開發者介面，你可以傳遞回呼或 await \"nextTick\"．\n\n```ts\nimport { createApp, h, reactive, nextTick } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({\n      count: 0,\n    })\n    const updateState = async () => {\n      state.count++\n\n      await nextTick() // 等待\n      const p = document.getElementById('count-p')\n      if (p) {\n        console.log('😎 p.textContent', p.textContent)\n      }\n    }\n\n    return () => {\n      return h('div', { id: 'app' }, [\n        h('p', { id: 'count-p' }, [`${state.count}`]),\n        h('button', { onClick: updateState }, ['update']),\n      ])\n    }\n  },\n})\n\napp.mount('#app')\n```\n\n現在，讓我們實際重寫當前調度器的實現以保持\"currentFlushPromise\"並實現\"nextTick\"！\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/050_next_tick)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/20-basic-virtual-dom/040-patch-other-attrs.md",
    "content": "# 無法處理的 Props 的補丁\n\n在本章中，讓我們為目前無法處理的 Props 實現補丁．\n以下是一些需要處理的 Props 示例，但請嘗試透過參考原始實現來實現它們，同時自己填補缺失的部分！\n透過這樣做，它應該變得更加實用！\n\n沒有什麼特別新的東西．基於我們到目前為止所做的，應該能夠充分實現它．\n\n我想關注的是 runtime-dom/modules 的實現．\n\n## 新舊比較\n\n目前，更新只能基於 n2 的 props 進行．\n讓我們基於 n1 和 n2 進行更新．\n\n```ts\nconst oldProps = n1.props || {}\nconst newProps = n2.props || {}\n```\n\n存在於 n1 但不存在於 n2 中的 Props 應該被刪除．\n另外，如果即使兩者都存在但值相同，也不需要補丁，所以跳過它．\n\n## class / style（注意）\n\n有多種綁定 class 和 style 的方法．\n\n```html\n<p class=\"static property\">hello</p>\n<p :class=\"'dynamic property'\">hello</p>\n<p :class=\"['dynamic', 'property', 'array']\">hello</p>\n<p :class=\"{ dynamic: true, property: true, array: true}\">hello</p>\n<p class=\"static property\" :class=\"'mixed dynamic property'\">hello</p>\n<p style=\"static: true;\" :style=\"{ mixed-dynamic: 'true' }\">hello</p>\n```\n\n要實現這些，需要在基礎模板編譯器部分解釋的 `transform` 概念．\n只要不偏離原始 Vue 的設計，它可以在任何地方實現，但我們在這裡跳過它，因為我們想在本書中遵循原始 Vue 的設計．\n\n## innerHTML / textContent\n\ninnerHTML 和 textContent 與其他 Props 相比有點特殊．\\\n這是因為如果具有此 Prop 的元素有子元素，它們需要被卸載．\n\n例如，考慮以下情況：\n\n```ts\nh('div', { innerHTML: '<p>hello</p>' }, [\n  h(SomeComponent, {}, [])\n])\n```\n\n在這種情況下，div 元素的內容將被 `innerHTML` 覆蓋為 `<p>hello</p>`．\\\n然而，作為 children 傳遞的 `SomeComponent` 已經存在於虛擬 DOM 中，如果不正確卸載它，將會發生以下問題：\n\n- 事件監聽器不會被移除\n- 組件生命週期鉤子（如 onUnmounted）不會被調用\n- 可能導致記憶體洩漏\n\n因此，在設定 innerHTML 或 textContent 時，需要卸載現有的子元素．\n\n### 實現\n\n首先，擴展 `patchProp` 的類型定義以接受 `prevChildren` 和 `unmountChildren`．\n\n`~/packages/runtime-core/renderer.ts`\n\n```ts\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(\n    el: HostElement,\n    key: string,\n    prevValue: any,\n    nextValue: any,\n    prevChildren?: VNode<HostNode>[], // 添加\n    unmountChildren?: (children: VNode<HostNode>[]) => void, // 添加\n  ): void;\n  // ...\n}\n```\n\n接下來，在 `patchDOMProp` 函式中實現 innerHTML/textContent 的處理．\n\n`~/packages/runtime-dom/modules/props.ts`\n\n```ts\nexport function patchDOMProp(\n  el: any,\n  key: string,\n  value: any,\n  prevChildren: any,\n  unmountChildren: any,\n) {\n  if (key === 'innerHTML' || key === 'textContent') {\n    // 如果存在子元素則卸載\n    if (prevChildren) {\n      unmountChildren(prevChildren)\n    }\n    el[key] = value == null ? '' : value\n    return\n  }\n\n  // ... (其他 props 的處理)\n}\n```\n\n然後，在從 `patchProp` 調用 `patchDOMProp` 時傳遞 `prevChildren` 和 `unmountChildren`．\n\n`~/packages/runtime-dom/patchProp.ts`\n\n```ts\nexport const patchProp: DOMRendererOptions['patchProp'] = (\n  el,\n  key,\n  prevValue,\n  nextValue,\n  prevChildren,\n  unmountChildren,\n) => {\n  if (key === 'style') {\n    patchStyle(el, prevValue, nextValue)\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue)\n  } else if (shouldSetAsProp(el, key)) {\n    patchDOMProp(el, key, nextValue, prevChildren, unmountChildren) // 傳遞 prevChildren, unmountChildren\n  } else {\n    patchAttr(el, key, nextValue)\n  }\n}\n```\n\n最後，在 renderer.ts 中調用 `hostPatchProp` 時傳遞適當的參數．\n\n`~/packages/runtime-core/renderer.ts` 的 `mountElement` 和 `patchElement`\n\n```ts\nconst mountElement = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {\n  let el: RendererElement\n  const { type, props } = vnode\n  el = vnode.el = hostCreateElement(type as string)\n\n  mountChildren(vnode.children as VNode[], el, anchor)\n\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(\n        el,\n        key,\n        null,\n        props[key],\n        vnode.children as VNode[], // 添加\n        unmountChildren, // 添加\n      )\n    }\n  }\n\n  hostInsert(el, container)\n}\n```\n\n現在，當使用 innerHTML 或 textContent 時，現有的子元素將被正確卸載．\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/20_basic_virtual_dom/060_other_props)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/30-basic-reactivity-system/005-reactivity-optimization.md",
    "content": "# 響應式優化\n\n::: info 關於本章\n本章介紹 Vue 3.6 中將引入的基於 [alien-signals](https://github.com/stackblitz/alien-signals) 的響應式系統優化．\\\nchibivue 的實現也基於此演算法進行了更新．\n:::\n\n## 背景\n\nVue.js 的響應式系統在 Vue 3.4 中進行了重大性能優化．然而，Vue 3.5 切換到了類似 Preact 的 pull-based 演算法，改變了響應式系統的方向．\n\n為了進一步研究 push-pull based 實現，Vue 的核心貢獻者 Johnson Chu 開發了獨立專案 [alien-signals](https://github.com/stackblitz/alien-signals)．\n\nalien-signals 是基於 Vue 3.4 響應式系統重新實現的訊號庫，具有以下特點：\n\n- **輕量**：最小的記憶體佔用\n- **快速**：約為 Vue 3.4 響應式系統的 4 倍（400%）效能\n- **記憶體高效**：約 13% 的記憶體使用量減少\n\n這些成果將在 Vue 3.6 中被移植到 Vue 核心的響應式系統中．\n\n參考：[vuejs/core#12349](https://github.com/vuejs/core/pull/12349)\n\n<KawaikoNote variant=\"surprise\" title=\"效能提升 4 倍！\">\n\nalien-signals 基於 Vue 3.4 的響應式系統重新實現，竟然實現了**約 4 倍**的效能提升！\\\n隨著這項成果被整合到 Vue 3.6 中，所有 Vue 使用者都將受益於這些優化．\n\n</KawaikoNote>\n\n## Push-Pull 響應式演算法\n\n讓我們簡要解釋 alien-signals 採用的 Push-Pull 演算法．\n\n### Push-based 與 Pull-based\n\n響應式系統主要有兩種方法：\n\n**Push-based（推送型）**\n\n當依賴項發生變化時，立即更新所有依賴的 computed 值．\n\n```\nsignal 變化 → 立即更新所有 computed → 執行 effect\n```\n\n優點：始終保證最新值\n缺點：即使未使用的 computed 也會被更新\n\n**Pull-based（拉取型）**\n\ncomputed 值僅在需要時（讀取時）才計算．\n\n```\nsignal 變化 → (不做任何事) → 在 effect 中讀取 computed → 此時計算\n```\n\n優點：僅執行必要的計算\n缺點：讀取時有開銷\n\n### Push-Pull（混合型）\n\nalien-signals 和 Vue 3.6 採用的 Push-Pull 演算法結合了兩者的優點：\n\n1. **Push 階段**：當 signal 變化時，在依賴的 computed 上設置「dirty」標誌\n2. **Pull 階段**：當讀取 computed 時，如果是 dirty 則重新計算\n\n```\nsignal 變化 → 傳播 dirty 標誌 → 在 effect 中讀取 computed → 如果 dirty 則重新計算\n```\n\n![Push-Pull reactivity overview](/figures/30-basic-reactivity-system/reactivity-optimization/push-pull-overview.svg)\n\n這種方法提供：\n- 避免不必要的計算（Pull 的優點）\n- 高效的依賴追蹤（Push 的優點）\n\n<KawaikoNote variant=\"funny\" title=\"兩全其美！\">\n\nPush-Pull 演算法是一種結合了 Push 和 Pull 兩者優點的聰明方法．\\\n「發生變化時只傳播 dirty 標誌，實際計算等到需要時再做」的策略，徹底消除了不必要的計算！\n\n</KawaikoNote>\n\n## alien-signals 的基本 API\n\nalien-signals 提供了非常簡單的 API：\n\n```ts\nimport { signal, computed, effect } from 'alien-signals'\n\n// signal：建立響應式值\nconst count = signal(1)\n\n// 讀取值\nconsole.log(count()) // 1\n\n// 更新值\ncount(2)\n\n// computed：建立衍生值\nconst double = computed(() => count() * 2)\nconsole.log(double()) // 4\n\n// effect：註冊副作用\neffect(() => {\n  console.log(`Count is: ${count()}`)\n})\n\ncount(3) // 輸出 \"Count is: 3\"\n```\n\n與 Vue 的 `ref` 和 `reactive` 比較：\n\n| alien-signals | Vue |\n|--------------|-----|\n| `signal(value)` | `ref(value)` |\n| `signal()` 讀取 | `.value` 讀取 |\n| `signal(newValue)` 寫入 | `.value = newValue` 寫入 |\n| `computed(() => ...)` | `computed(() => ...)` |\n| `effect(() => ...)` | `watchEffect(() => ...)` |\n\n## 實現概述\n\n::: warning\n本章不會完全移植 alien-signals 的實現，而是解釋其概念和基本機制．\\\n要完全理解，請參閱 [alien-signals 原始碼](https://github.com/stackblitz/alien-signals) 或 [Vue 3.6 的 PR](https://github.com/vuejs/core/pull/12349)．\n:::\n\n<KawaikoNote variant=\"base\" title=\"請查看 Johnson 的解說！\">\n\n如果您想了解更多關於 alien-signals 演算法的內容，我們推薦閱讀作者 Johnson Chu 撰寫的解說！\\\n[https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30)\n\n</KawaikoNote>\n\n### 雙向連結串列\n\nalien-signals 的重要優化之一是使用雙向連結串列管理依賴關係．\n\n傳統的 Vue 實現使用 Set 管理依賴：\n\n```ts\n// 傳統實現\nclass Dep {\n  subscribers = new Set<ReactiveEffect>()\n\n  track() {\n    if (activeEffect) {\n      this.subscribers.add(activeEffect)\n    }\n  }\n\n  trigger() {\n    this.subscribers.forEach(effect => effect.run())\n  }\n}\n```\n\nalien-signals 使用連結串列：\n\n```ts\n// alien-signals 風格\ninterface Link {\n  dep: Dep\n  sub: Subscriber\n  prevDep: Link | undefined  // 同一 subscriber 的前一個 dep 的參考\n  nextDep: Link | undefined  // 同一 subscriber 的下一個 dep 的參考\n  prevSub: Link | undefined  // 同一 dep 的前一個 subscriber 的參考\n  nextSub: Link | undefined  // 同一 dep 的下一個 subscriber 的參考\n}\n```\n\n這種結構提供：\n- 減少記憶體使用（避免 Set 的開銷）\n- O(1) 的依賴添加/刪除\n- 減少 GC 壓力\n\n### 版本管理\n\n另一個重要優化是使用版本號進行 dirty 檢查：\n\n```ts\nlet globalVersion = 0\n\nfunction triggerRef(ref: Ref) {\n  globalVersion++\n  ref.version = globalVersion\n  // 向 subscribers 傳播 dirty\n}\n\nfunction computedGetter(computed: ComputedRef) {\n  if (computed.globalVersion !== globalVersion) {\n    // 依賴項之一可能已更新\n    if (checkDirty(computed)) {\n      // 如果實際是 dirty 則重新計算\n      computed.value = computed.getter()\n    }\n    computed.globalVersion = globalVersion\n  }\n  return computed.value\n}\n```\n\n使用全域版本提供：\n- 高效判斷 computed 是否真的需要重新計算\n- 避免不必要的依賴遍歷\n\n## chibivue 中的實現\n\nchibivue 基於 alien-signals 演算法實現響應式系統．\n\n主要檔案：\n- `packages/reactivity/dep.ts` - 依賴管理\n- `packages/reactivity/effect.ts` - effect 實現\n- `packages/reactivity/ref.ts` - ref 實現\n- `packages/reactivity/computed.ts` - computed 實現\n\n基本結構：\n\n```ts\n// packages/reactivity/dep.ts\nexport interface Link {\n  dep: Dep\n  sub: Subscriber\n  version: number\n  prevDep: Link | undefined\n  nextDep: Link | undefined\n  prevSub: Link | undefined\n  nextSub: Link | undefined\n}\n\nexport class Dep {\n  version = 0\n  link: Link | undefined = undefined\n  subs: Link | undefined = undefined\n\n  track(): Link | undefined {\n    // 將 activeEffect 註冊為訂閱者\n  }\n\n  trigger(): void {\n    // 通知所有訂閱者\n  }\n}\n```\n\n```ts\n// packages/reactivity/effect.ts\nexport class ReactiveEffect<T = any> implements Subscriber {\n  deps: Link | undefined = undefined\n  depsTail: Link | undefined = undefined\n\n  run(): T {\n    // 執行 effect 函數並收集依賴\n  }\n}\n```\n\n後續章節將基於這個優化的響應式系統進行構建．\n\n<KawaikoNote variant=\"base\" title=\"繼續前進\">\n\n你理解 alien-signals 的概念了嗎？\\\n連結串列和版本管理一開始可能感覺很難，但隨著你編寫程式碼，自然會理解的．\\\n讓我們在下一章中基於這個優化的機制實現 ref 和 computed！\n\n</KawaikoNote>\n\n## 總結\n\n- Vue 3.6 將引入基於 alien-signals 的優化響應式系統\n- Push-Pull 演算法實現高效的 dirty 檢查和延遲評估\n- 雙向連結串列的依賴管理提高記憶體效率\n- 基於版本號的 dirty 檢查避免不必要的重新計算\n\n從下一章開始，我們將在這個優化的響應式系統之上實現 ref 和 computed 等 API．\n\n## 參考連結\n\n- [stackblitz/alien-signals](https://github.com/stackblitz/alien-signals) - alien-signals 官方儲存庫\n- [alien-signals 演算法詳細解說](https://gist.github.com/johnsoncodehk/59e79a0cfa5bb3421b5d166a08e42f30) - 作者 Johnson Chu 撰寫的詳細解說\n- [vuejs/core#12349](https://github.com/vuejs/core/pull/12349) - Vue 3.6 移植 PR\n- [掌握 Vue 3.6 的 Alien Signals](https://medium.com/@revanthkumarpatha/mastering-vue-3-6s-alien-signals-practical-examples-and-use-cases-7df02a159d8a) - Medium 文章\n"
  },
  {
    "path": "book/online-book/src/zh-tw/30-basic-reactivity-system/010-ref-api.md",
    "content": "# ref api（基礎響應式系統開始）\n\n::: tip 前置知識\n在閱讀本章之前，我們建議閱讀[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)以了解基於 alien-signals 的優化響應式系統的概念．\n:::\n\n## ref api 的回顧（和實現）\n\nVue.js 有各種與響應式相關的 API，其中 ref 特別著名．  \n即使在官方文件中，它也作為\"響應式核心\"名稱下的第一個主題被介紹．  \nhttps://vuejs.org/api/reactivity-core.html#ref\n\n那麼，什麼是 ref API？\n根據官方文件，\n\n> ref 物件是可變的 - 即你可以為 .value 分配新值。它也是響應式的 - 即對 .value 的任何讀取操作都會被追蹤，寫入操作會觸發相關的 effect。\n\n> 如果將物件分配為 ref 的值，該物件會透過 reactive() 變成深度響應式的。這也意味著如果物件包含嵌套的 ref，它們將被深度解包。\n\n（引用：https://vuejs.org/api/reactivity-core.html#ref）\n\n簡而言之，ref 物件有兩個特徵：\n\n- 對 value 屬性的獲取/設置操作會觸發 track/trigger．\n- 當物件被分配給 value 屬性時，value 屬性變成響應式物件．\n\n用程式碼來解釋，\n\n```ts\nconst count = ref(0)\ncount.value++ // effect（特徵 1）\n\nconst state = ref({ count: 0 })\nstate.value = { count: 1 } // effect（特徵 1）\nstate.value.count++ // effect（特徵 2）\n```\n\n就是這個意思．\n\n在你能夠區分 ref 和 reactive 之前，你可能會混淆 `ref(0)` 和 `reactive({ value: 0 })` 之間的區別，但考慮到上面提到的兩個特徵，你可以看到它們有完全不同的含義．\nref 不會生成像 `{ value: x }` 這樣的響應式物件．對 value 的獲取/設置操作的 track/trigger 是由 ref 的實現執行的，如果對應於 x 的部分是物件，它就會變成響應式物件．\n\n在實現方面，它看起來像這樣：\n\n```ts\nclass RefImpl<T> {\n  private _value: T\n  public dep?: Dep = undefined\n\n  get value() {\n    trackRefValue(this)\n  }\n\n  set value(newVal) {\n    this._value = toReactive(newVal)\n    triggerRefValue(this)\n  }\n}\n\nconst toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value\n```\n\n讓我們在查看原始碼的同時實現 ref！\n有各種函式和類別，但現在，專注於 RefImpl 類別和 ref 函式就足夠了．\n\n一旦你能執行以下原始碼，就可以了！\n（注意：模板編譯器需要單獨支援 ref，所以它不會工作）\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = ref(0)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['Increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/010_ref)\n\n## shallowRef\n\n現在，讓我們繼續實現更多與 ref 相關的 API．  \n如前所述，ref 的特徵之一是\"當物件被分配給 value 屬性時，value 屬性變成響應式物件\"．shallowRef 沒有這個特徵．\n\n> 與 ref() 不同，淺層 ref 的內部值按原樣儲存和暴露，不會被深度響應式化。只有 .value 存取是響應式的。\n\n（引用：https://vuejs.org/api/reactivity-advanced.html#shallowref）\n\n任務非常簡單．我們可以按原樣使用 RefImpl 的實現，並跳過 `toReactive` 部分．  \n讓我們在閱讀原始碼的同時實現它！\n\n一旦你能執行以下原始碼，就可以了！\n\n```ts\nimport { createApp, h, shallowRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // 點擊不會觸發重新渲染\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n### triggerRef\n\n如前所述，由於 shallow ref 的值不是響應式物件，對它的更改不會觸發 effect．  \n然而，值本身是一個物件，所以它已經被更改了．  \n因此，有一個 API 可以強制觸發 effect．它就是 triggerRef．\n\nhttps://vuejs.org/api/reactivity-advanced.html#triggerref\n\n```ts\nimport { createApp, h, shallowRef, triggerRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = shallowRef({ count: 0 })\n    const forceUpdate = () => {\n      triggerRef(state)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.value.count}`]),\n\n        h(\n          'button',\n          {\n            onClick: () => {\n              state.value = { count: state.value.count + 1 }\n            },\n          },\n          ['increment'],\n        ),\n\n        h(\n          'button', // 點擊不會觸發重新渲染\n          {\n            onClick: () => {\n              state.value.count++\n            },\n          },\n          ['not trigger ...'],\n        ),\n\n        h(\n          'button', // 渲染更新為 state.value.count 當前持有的值\n          { onClick: forceUpdate },\n          ['force update !'],\n        ),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/020_shallow_ref)\n\n## toRef\n\ntoRef 是一個為響應式物件的屬性生成 ref 的 API．\n\nhttps://vuejs.org/api/reactivity-utilities.html#toref\n\n它經常用於將 props 的特定屬性轉換為 ref．\n\n```ts\nconst count = toRef(props, 'count')\nconsole.log(count.value)\n```\n\n由 toRef 創建的 ref 與原始響應式物件同步．\n如果你對這個 ref 進行更改，原始響應式物件也會被更新，如果原始響應式物件有任何更改，這個 ref 也會被更新．\n\n```ts\nimport { createApp, h, reactive, toRef } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const stateCountRef = toRef(state, 'count')\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`state.count: ${state.count}`]),\n        h('p', {}, [`stateCountRef.value: ${stateCountRef.value}`]),\n        h('button', { onClick: () => state.count++ }, ['updateState']),\n        h('button', { onClick: () => stateCountRef.value++ }, ['updateRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n讓我們在閱讀原始碼的同時實現！\n\n※ 從 v3.3 開始，toRef 添加了規範化功能．chibivue 不實現此功能．  \n請查看官方文件中的簽名以獲取更多詳細資訊！（https://vuejs.org/api/reactivity-utilities.html#toref）\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/030_to_ref)\n\n## toRefs\n\n為響應式物件的所有屬性生成 ref．\n\nhttps://vuejs.org/api/reactivity-utilities.html#torefs\n\n```ts\nimport { createApp, h, reactive, toRefs } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ foo: 1, bar: 2 })\n    const stateAsRefs = toRefs(state)\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`[state]: foo: ${state.foo}, bar: ${state.bar}`]),\n        h('p', {}, [\n          `[stateAsRefs]: foo: ${stateAsRefs.foo.value}, bar: ${stateAsRefs.bar.value}`,\n        ]),\n        h('button', { onClick: () => state.foo++ }, ['update state.foo']),\n        h('button', { onClick: () => stateAsRefs.bar.value++ }, [\n          'update stateAsRefs.bar.value',\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n這可以使用 toRef 的實現輕鬆實現．\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/040_to_refs)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/30-basic-reactivity-system/020-computed-watch.md",
    "content": "# computed / watch api\n\n::: warning\n這裡解釋的實現基於當前草擬的[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的內容將更新以與其保持一致．\n:::\n\n## computed 的回顧（和實現）\n\n在上一章中，我們實現了與 ref 相關的 API．接下來，讓我們談談 computed．\nhttps://vuejs.org/api/reactivity-core.html#computed\n\nComputed 有兩個簽名：唯讀和可寫．\n\n```ts\n// 唯讀\nfunction computed<T>(\n  getter: () => T,\n  // 參見下面的\"Computed 除錯\"連結\n  debuggerOptions?: DebuggerOptions,\n): Readonly<Ref<Readonly<T>>>\n\n// 可寫\nfunction computed<T>(\n  options: {\n    get: () => T\n    set: (value: T) => void\n  },\n  debuggerOptions?: DebuggerOptions,\n): Ref<T>\n```\n\n官方實現有點複雜，但讓我們從一個簡單的結構開始．\n\n實現它的最簡單方法是每次檢索值時觸發回呼．\n\n```ts\nexport class ComputedRefImpl<T> {\n  constructor(private getter: ComputedGetter<T>) {}\n\n  get value() {\n    return this.getter()\n  }\n\n  set value() {}\n}\n```\n\n然而，這並不是真正的 computed．它只是呼叫一個函式（這並不是很令人興奮）．\n\n實際上，我們希望追蹤依賴項並在值更改時重新計算．\n\n為了實現這一點，我們使用一種機制，將 `_dirty` 標誌作為調度器作業更新．\n`_dirty` 標誌是一個表示值是否需要重新計算的標誌．它在被依賴項觸發時更新．\n\n以下是它的工作原理示例：\n\n```ts\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined\n  private _value!: T\n  public readonly effect: ReactiveEffect<T>\n  public _dirty = true\n\n  constructor(getter: ComputedGetter<T>) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true\n      }\n    })\n  }\n\n  get value() {\n    trackRefValue(this)\n    if (this._dirty) {\n      this._dirty = false\n      this._value = this.effect.run()\n    }\n    return this._value\n  }\n}\n```\n\nComputed 實際上具有惰性求值的性質，所以值只在第一次讀取時重新計算．\n我們將此標誌更新為 true，函式被多個依賴項觸發，所以我們將其註冊為 ReactiveEffect 的調度器．\n\n這是基本流程．在實現時，有幾個要注意的點，讓我們在下面總結它們．\n\n- 當將 `_dirty` 標誌更新為 true 時，觸發它擁有的依賴項．\n  ```ts\n  if (!this._dirty) {\n    this._dirty = true\n    triggerRefValue(this)\n  }\n  ```\n- 由於 computed 被歸類為 `ref`，將 `__v_isRef` 標記為 true．\n- 如果你想實現 setter，最後實現它．首先，目標是使其可計算．\n\n現在我們準備好了，讓我們實現它！如果下面的程式碼按預期工作，就可以了！（請確保只觸發 computed 依賴項！）\n\n```ts\nimport { computed, createApp, h, reactive, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const count = reactive({ value: 0 })\n    const count2 = reactive({ value: 0 })\n    const double = computed(() => {\n      console.log('computed')\n      return count.value * 2\n    })\n    const doubleDouble = computed(() => {\n      console.log('computed (doubleDouble)')\n      return double.value * 2\n    })\n\n    const countRef = ref(0)\n    const doubleCountRef = computed(() => {\n      console.log('computed (doubleCountRef)')\n      return countRef.value * 2\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${count.value}`]),\n        h('p', {}, [`count2: ${count2.value}`]),\n        h('p', {}, [`double: ${double.value}`]),\n        h('p', {}, [`doubleDouble: ${doubleDouble.value}`]),\n        h('p', {}, [`doubleCountRef: ${doubleCountRef.value}`]),\n        h('button', { onClick: () => count.value++ }, ['update count']),\n        h('button', { onClick: () => count2.value++ }, ['update count2']),\n        h('button', { onClick: () => countRef.value++ }, ['update countRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/050_computed)\n（帶 setter）：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/060_computed_setter)\n\n## Watch 的實現\n\nhttps://vuejs.org/api/reactivity-core.html#watch\n\nwatch API 有各種形式．讓我們從實現最簡單的形式開始，即使用 getter 函式進行監視．\n首先，讓我們目標使下面的程式碼工作．\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    watch(\n      () => state.count,\n      () => alert('state.count was changed!'),\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: () => state.count++ }, ['update state']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\nwatch 的實現不在 reactivity 中，而在 runtime-core（apiWatch.ts）中．\n\n它可能看起來有點複雜，因為有各種 API 混合在一起，但如果你縮小範圍，實際上非常簡單．\n我已經在下面實現了目標 API（watch 函式）的簽名，所以請嘗試實現它．我相信如果你到目前為止已經掌握了響應式的知識，你可以做到！\n\n```ts\nexport type WatchEffect = (onCleanup: OnCleanup) => void\n\nexport type WatchSource<T = any> = () => T\n\ntype OnCleanup = (cleanupFn: () => void) => void\n\nexport function watch<T>(\n  source: WatchSource<T>,\n  cb: (newValue: T, oldValue: T) => void,\n) {\n  // TODO:\n}\n```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/070_watch)\n\n## watch 的其他 API\n\n一旦你有了基礎，就只是擴展的問題．不需要進一步解釋．\n\n- 監視 ref\n  ```ts\n  const count = ref(0)\n  watch(count, () => {\n    /** some effects */\n  })\n  ```\n- 監視多個源\n\n  ```ts\n  const count = ref(0)\n  const count2 = ref(0)\n  const count3 = ref(0)\n  watch([count, count2, count3], () => {\n    /** some effects */\n  })\n  ```\n\n- Immediate\n\n  ```ts\n  const count = ref(0)\n  watch(\n    count,\n    () => {\n      /** some effects */\n    },\n    { immediate: true },\n  )\n  ```\n\n- Deep\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(\n    () => state,\n    () => {\n      /** some effects */\n    },\n    { deep: true },\n  )\n  ```\n\n- 響應式物件\n\n  ```ts\n  const state = reactive({ count: 0 })\n  watch(state, () => {\n    /** some effects */\n  }) // 自動進入深度模式\n  ```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/080_watch_api_extends)\n\n## watchEffect\n\nhttps://vuejs.org/api/reactivity-core.html#watcheffect\n\n使用 watch 實現來實現 watchEffect 很容易．\n\n```ts\nconst count = ref(0)\n\nwatchEffect(() => console.log(count.value))\n// -> 記錄 0\n\ncount.value++\n// -> 記錄 1\n```\n\n你可以像 immediate 的圖像一樣實現它．\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/090_watch_effect)\n\n---\n\n※ 清理將在單獨的章節中完成．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/30-basic-reactivity-system/030-reactive-proxy-handlers.md",
    "content": "# 各種響應式代理處理器\n\n::: warning\n這裡解釋的實現基於當前草擬的[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的內容將更新以與其保持一致．\n:::\n\n## 不應該是響應式的物件\n\n現在，讓我們解決當前響應式系統的一個問題．  \n首先，嘗試執行以下程式碼．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n如果你檢查控制台，你應該看到以下結果：\n\n![Reactive HTML element console output](/figures/30-basic-reactivity-system/reactive-proxy-handlers/reactive-html-element.png)\n\n現在，讓我們添加一個焦點函式．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value)\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n令人驚訝的是，它拋出了一個錯誤．\n\n![Focus result in a reactive HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-reactive-html-element.png)\n\n原因是 `document.getElementById` 獲得的元素被用來生成 Proxy 本身．\n\n當生成 Proxy 時，值變成 Proxy 而不是原始物件，導致 HTML 元素功能的丟失．\n\n## 在生成響應式代理之前確定物件\n\n確定方法非常簡單．使用 `Object.prototype.toString`．\n讓我們看看 `Object.prototype.toString` 如何在上面的程式碼中確定 HTMLInputElement．\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const getRef = () => {\n      inputRef.value = document.getElementById(\n        'my-input',\n      ) as HTMLInputElement | null\n      console.log(inputRef.value?.toString())\n    }\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { id: 'my-input' }, []),\n        h('button', { onClick: getRef }, ['getRef']),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n![HTMLElement toString result](/figures/30-basic-reactivity-system/reactive-proxy-handlers/element-to-string.png)\n\n這允許我們確定物件的類型．雖然有些硬編碼，但讓我們概括這個確定函式．\n\n```ts\n// shared/general.ts\nexport const objectToString = Object.prototype.toString // 已在 isMap 和 isSet 中使用\nexport const toTypeString = (value: unknown): string =>\n  objectToString.call(value)\n\n// 這次要添加的函式\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1)\n}\n```\n\n使用 `slice` 的原因是獲取 `[Object hoge]` 中對應於 `hoge` 的字串．\n\n然後，讓我們透過使用 `reactive toRawType` 確定物件的類型並進行分支．\n跳過為 HTMLInput 生成 Proxy．\n\n在 reactive.ts 中，獲取 rawType 並確定將成為 reactive 目標的物件類型．\n\n```ts\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value)\n    ? TargetType.INVALID\n    : targetTypeMap(toRawType(value))\n}\n```\n\n```ts\nexport function reactive<T extends object>(target: T): T {\n  const targetType = getTargetType(target)\n  if (targetType === TargetType.INVALID) {\n    return target\n  }\n\n  const proxy = new Proxy(target, mutableHandlers)\n  return proxy as T\n}\n```\n\n現在，焦點程式碼應該工作了！\n\n![Focus result in a plain HTML element](/figures/30-basic-reactivity-system/reactive-proxy-handlers/focus-in-element.png)\n\n## 實現 TemplateRefs\n\n現在我們可以將 HTML 元素放入 Ref 中，讓我們實現 TemplateRef．\n\nRef 可以透過使用 ref 屬性來引用模板．\n\nhttps://vuejs.org/guide/essentials/template-refs.html\n\n目標是使以下程式碼工作：\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const inputRef = ref<HTMLInputElement | null>(null)\n    const focus = () => {\n      inputRef.value?.focus()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('input', { ref: inputRef }, []),\n        h('button', { onClick: focus }, ['focus']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n如果你已經走到這一步，你可能已經看到如何實現它．\n是的，只需將 ref 添加到 VNode 並在渲染期間注入值．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  key: string | number | symbol | null\n  ref: Ref | null // 這個\n  // .\n  // .\n}\n```\n\n在原始實現中，它被稱為 `setRef`．找到它，閱讀它，並實現它！\n在原始實現中，它更複雜，ref 是一個陣列並且可以透過 `$ref` 存取，但現在，讓我們目標使上面的程式碼工作．\n\n順便說一下，如果它是一個組件，將組件的 `setupContext` 分配給 ref．  \n（注意：實際上，你應該傳遞組件的代理，但它還沒有實現，所以我們現在使用 `setupContext`．）\n\n```ts\nimport { createApp, h, ref } from 'chibivue'\n\nconst Child = {\n  setup() {\n    const action = () => alert('clicked!')\n    return { action }\n  },\n\n  template: `<button @click=\"action\">action (child)</button>`,\n}\n\nconst app = createApp({\n  setup() {\n    const childRef = ref<any>(null)\n    const childAction = () => {\n      childRef.value?.action()\n    }\n\n    return () =>\n      h('div', {}, [\n        h('div', {}, [\n          h(Child, { ref: childRef }, []),\n          h('button', { onClick: childAction }, ['action (parent)']),\n        ]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/110_template_refs)\n\n## 處理具有變化鍵的物件\n\n實際上，當前的實現無法處理具有變化鍵的物件．\n這也包括陣列．\n換句話說，以下組件無法正常工作：\n\n```ts\nconst App = {\n  setup() {\n    const array = ref<number[]>([])\n    const mutateArray = () => {\n      array.value.push(Date.now()) // 即使呼叫這個也不會觸發 effect（set 的鍵是 \"0\"）\n    }\n\n    const record = reactive<Record<string, number>>({})\n    const mutateRecord = () => {\n      record[Date.now().toString()] = Date.now() // 即使鍵改變也不會觸發 effect\n    }\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`array: ${JSON.stringify(array.value)}`]),\n        h('button', { onClick: mutateArray }, ['update array']),\n\n        h('p', {}, [`record: ${JSON.stringify(record)}`]),\n        h('button', { onClick: mutateRecord }, ['update record']),\n      ])\n  },\n}\n```\n\n我們如何解決這個問題？\n\n### 對於陣列\n\n陣列本質上是物件，所以當添加新元素時，其索引作為鍵傳遞給 Proxy 的 `set` 處理器．\n\n```ts\nconst p = new Proxy([], {\n  set(target, key, value, receiver) {\n    console.log(key) // ※\n    Reflect.set(target, key, value, receiver)\n    return true\n  },\n})\n\np.push(42) // 0\n```\n\n然而，我們無法單獨追蹤這些鍵中的每一個．\n因此，我們可以追蹤陣列的 `length` 來觸發陣列的變化．\n\n值得注意的是，`length` 已經被追蹤了．\n\n如果你在瀏覽器或類似環境中執行以下程式碼，你會看到當使用 `JSON.stringify` 字串化陣列時會呼叫 `length`．\n\n```ts\nconst data = new Proxy([], {\n  get(target, key) {\n    console.log('get!', key)\n    return Reflect.get(target, key)\n  },\n})\n\nJSON.stringify(data)\n// get! length\n// get! toJSON\n```\n\n換句話說，`length` 已經註冊了一個 effect．所以，我們需要做的就是提取這個 effect 並在設置索引時觸發它．\n\n如果鍵被確定為索引，我們觸發 `length` 的 effect．\n當然，可能還有其他依賴項，所以我們將它們提取到一個名為 `deps` 的陣列中並一起觸發 effect．\n\n```ts\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  // 這個\n  if (isIntegerKey(key)) {\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n```ts\n// shared/general.ts\nexport const isIntegerKey = (key: unknown) =>\n  isString(key) &&\n  key !== 'NaN' &&\n  key[0] !== '-' &&\n  '' + parseInt(key, 10) === key\n```\n\n現在，陣列應該正常工作了．\n\n### 對於物件（記錄）\n\n接下來，讓我們考慮物件．與陣列不同，物件沒有 `length` 屬性．\n\n我們可以在這裡做一個小修改．\n我們可以準備一個名為 `ITERATE_KEY` 的符號，並以類似於陣列的 `length` 屬性的方式使用它．\n你可能不理解我的意思，但由於 `depsMap` 只是一個 Map，使用我們定義的符號作為鍵沒有問題．\n\n操作順序與陣列略有不同，但讓我們從考慮 `trigger` 函式開始．\n我們可以實現它，就好像有一個註冊了 effect 的 `ITERATE_KEY`．\n\n```ts\nexport const ITERATE_KEY = Symbol()\n\nexport function trigger(target: object, key?: unknown) {\n  const depsMap = targetMap.get(target)\n  if (!depsMap) return\n\n  let deps: (Dep | undefined)[] = []\n  if (key !== void 0) {\n    deps.push(depsMap.get(key))\n  }\n\n  if (!isArray(target)) {\n    // 如果不是陣列，觸發用 ITERATE_KEY 註冊的 effect\n    deps.push(depsMap.get(ITERATE_KEY))\n  } else if (isIntegerKey(key)) {\n    // 向陣列添加新索引 -> length 改變\n    deps.push(depsMap.get('length'))\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep)\n    }\n  }\n}\n```\n\n問題是如何追蹤 `ITERATE_KEY` 的 effect．\n\n在這裡，我們可以使用 `ownKeys` Proxy 處理器．\n\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys\n\n`ownKeys` 被 `Object.keys()` 或 `Reflect.ownKeys()` 等函式呼叫，但它也被 `JSON.stringify` 呼叫．\n\n你可以透過在瀏覽器或類似環境中執行以下程式碼來確認這一點：\n\n```ts\nconst data = new Proxy(\n  {},\n  {\n    get(target, key) {\n      return Reflect.get(target, key)\n    },\n    ownKeys(target) {\n      console.log('ownKeys!!!')\n      return Reflect.ownKeys(target)\n    },\n  },\n)\n\nJSON.stringify(data)\n```\n\n我們可以使用這個來追蹤 `ITERATE_KEY`．\n對於陣列，我們不需要它，所以我們可以簡單地追蹤 `length`．\n\n```ts\nexport const mutableHandlers: ProxyHandler<object> = {\n  // .\n  // .\n  ownKeys(target) {\n    track(target, isArray(target) ? 'length' : ITERATE_KEY)\n    return Reflect.ownKeys(target)\n  },\n}\n```\n\n現在，我們應該能夠處理具有變化鍵的物件了！\n\n## 支援基於集合的內建物件\n\n目前，在查看 reactive.ts 的實現時，它只針對 Object 和 Array．\n\n```ts\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case 'Object':\n    case 'Array':\n      return TargetType.COMMON\n    default:\n      return TargetType.INVALID\n  }\n}\n```\n\n在 Vue.js 中，除了這些，它還支援 Map，Set，WeakMap 和 WeakSet．\n\nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/reactive.ts#L43C1-L56C2\n\n這些物件被實現為單獨的 Proxy 處理器．它被稱為 `collectionHandlers`．\n\n在這裡，我們將實現這個 `collectionHandlers` 並目標使以下程式碼工作．\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ map: new Map(), set: new Set() })\n\n    return () =>\n      h('div', {}, [\n        h('h1', {}, [`ReactiveCollection`]),\n\n        h('p', {}, [\n          `map (${state.map.size}): ${JSON.stringify([...state.map])}`,\n        ]),\n        h('button', { onClick: () => state.map.set(Date.now(), 'item') }, [\n          'update map',\n        ]),\n\n        h('p', {}, [\n          `set (${state.set.size}): ${JSON.stringify([...state.set])}`,\n        ]),\n        h('button', { onClick: () => state.set.add('item') }, ['update set']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n在 `collectionHandlers` 中，我們為 add，set 和 delete 等方法實現處理器．\n這些的實現可以在 `collectionHandlers.ts` 中找到．\nhttps://github.com/vuejs/core/blob/9f8e98af891f456cc8cc9019a31704e5534d1f08/packages/reactivity/src/collectionHandlers.ts#L0-L1\n透過確定 `TargetType`，如果它是集合類型，我們基於這個處理器為 `h` 生成 Proxy．\n讓我們實際實現它！\n\n需要注意的一點是，當將目標本身傳遞給 Reflect 的接收器時，如果目標本身設置了 Proxy，可能會導致無限循環．\n為了避免這種情況，我們改變結構，將原始資料附加到目標，當實現 Proxy 處理器時，我們修改它以在這個原始資料上操作．\n\n```ts\nexport const enum ReactiveFlags {\n  RAW = '__v_raw',\n}\n\nexport interface Target {\n  [ReactiveFlags.RAW]?: any\n}\n```\n\n嚴格來說，這個實現也應該為正常的響應式處理器完成，但為了最小化不必要的解釋並且因為到目前為止沒有問題，所以省略了．\n讓我們嘗試實現它，如果進入 getter 的鍵是 `ReactiveFlags.RAW`，它返回原始資料而不是 Proxy．\n\n與此同時，我們還實現了一個名為 `toRaw` 的函式，它遞迴地從目標檢索原始資料並最終獲得處於原始狀態的資料．\n\n```ts\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW]\n  return raw ? toRaw(raw) : observed\n}\n```\n\n順便說一下，這個 `toRaw` 函式也作為 API 函式提供．\n\nhttps://vuejs.org/api/reactivity-advanced.html#toraw\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/120_proxy_handler_improvement)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/30-basic-reactivity-system/040-effect-scope.md",
    "content": "# Effect 清理和 Effect 作用域\n\n::: warning\n這裡解釋的實現基於當前草擬的[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的內容將更新以與其保持一致．\n:::\n\n## ReactiveEffect 的清理\n\n到目前為止，我們還沒有清理註冊的 effect．讓我們為 ReactiveEffect 添加清理處理．\n\n在 ReactiveEffect 中實現一個名為 `stop` 的方法．  \n為 ReactiveEffect 添加一個標誌來指示它是否處於活動狀態，在 `stop` 方法中，將其切換為 `false` 同時刪除依賴項．\n\n```ts\nexport class ReactiveEffect<T = any> {\n  active = true // 添加\n  //.\n  //.\n  //.\n  stop() {\n    if (this.active) {\n      this.active = false\n    }\n  }\n}\n```\n\n有了這個基本實現，我們需要做的就是在執行 `stop` 方法時刪除所有依賴項．  \n此外，讓我們添加鉤子的實現，允許我們註冊在清理期間要執行的處理，以及當 `activeEffect` 是自身時的處理．\n\n```ts\nexport class ReactiveEffect<T = any> {\n  private deferStop?: boolean // 添加\n  onStop?: () => void // 添加\n  parent: ReactiveEffect | undefined = undefined // 添加（在 finally 中引用）\n\n  run() {\n    if (!this.active) {\n      return this.fn() // 如果 active 為 false，簡單地執行函式\n    }\n\n    try {\n      this.parent = activeEffect\n      activeEffect = this\n      const res = this.fn()\n      return res\n    } finally {\n      activeEffect = this.parent\n      this.parent = undefined\n      if (this.deferStop) {\n        this.stop()\n      }\n    }\n  }\n\n  stop() {\n    if (activeEffect === this) {\n      // 如果 activeEffect 是自身，設置標誌在 run 完成後停止\n      this.deferStop = true\n    } else if (this.active) {\n      // ...\n      if (this.onStop) {\n        this.onStop() // 執行註冊的鉤子\n      }\n      // ...\n    }\n  }\n}\n```\n\n現在我們已經為 ReactiveEffect 添加了清理處理，讓我們也為 watch 實現清理函式．\n\n如果以下程式碼工作，就可以了．\n\n```ts\nimport { createApp, h, reactive, watch } from 'chibivue'\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    const unwatch = watch(\n      () => state.count,\n      (newValue, oldValue, cleanup) => {\n        alert(`New value: ${newValue}, old value: ${oldValue}`)\n        cleanup(() => alert('Clean Up!'))\n      },\n    )\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`count: ${state.count}`]),\n        h('button', { onClick: increment }, [`increment`]),\n        h('button', { onClick: unwatch }, [`unwatch`]),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/130_cleanup_effects)\n\n## 什麼是 Effect 作用域\n\n現在我們可以清理 effect，我們希望在組件卸載時清理不必要的 effect．然而，收集大量的 effect（無論是 watch 還是 computed）有點麻煩．如果我們嘗試直接實現它，它會看起來像這樣：\n\n```ts\nlet disposables = []\n\nconst counter = ref(0)\n\nconst doubled = computed(() => counter.value * 2)\ndisposables.push(() => stop(doubled.effect))\n\nconst stopWatch = watchEffect(() => console.log(`counter: ${counter.value}`))\ndisposables.push(stopWatch)\n```\n\n```ts\n// 清理 effect\ndisposables.forEach(f => f())\ndisposables = []\n```\n\n這種管理方式很麻煩且容易出錯．\n\n因此，Vue 有一個稱為 EffectScope 的機制．  \nhttps://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md\n\n想法是每個實例有一個 EffectScope，具體來說，它有以下介面：\n\n```ts\nconst scope = effectScope()\n\nscope.run(() => {\n  const doubled = computed(() => counter.value * 2)\n\n  watch(doubled, () => console.log(doubled.value))\n\n  watchEffect(() => console.log('Count: ', doubled.value))\n})\n\n// 處理作用域中的所有 effect\nscope.stop()\n```\n\n引用自：https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md#basic-example\n\n這個 EffectScope 也作為面向使用者的 API 公開．  \nhttps://vuejs.org/api/reactivity-advanced.html#effectscope\n\n## EffectScope 的實現\n\n如前所述，我們將每個實例有一個 EffectScope．\n\n```ts\nexport interface ComponentInternalInstance {\n  scope: EffectScope\n}\n```\n\n當組件卸載時，我們停止收集的 effect．\n\n```ts\nconst unmountComponent = (...) => {\n  // .\n  // .\n  const { scope } = instance;\n  scope.stop();\n  // .\n  // .\n}\n```\n\nEffectScope 的結構如下：它有一個名為 `activeEffectScope` 的變數，指向當前活動的 EffectScope，並使用在 EffectScope 中實現的 `on/off/run/stop` 方法管理其狀態．  \n`on/off` 方法將自身提升為 `activeEffectScope` 或恢復提升狀態（返回到原始 EffectScope）．  \n當創建 ReactiveEffect 時，它在 `activeEffectScope` 中註冊．\n\n由於可能有點難以理解，如果我們在原始碼中寫出圖像，\n\n```ts\ninstance.scope.on()\n\n/** 創建一些 ReactiveEffect，如 computed 或 watch */\nsetup()\n\ninstance.scope.off()\n```\n\n透過這種方式，我們可以在實例的 EffectScope 中收集生成的 effect．  \n然後，當觸發此 effect 的 `stop` 方法時，我們可以清理所有 effect．\n\n你應該已經理解了基本原理，所以讓我們在閱讀原始碼的同時嘗試實現它！\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/140_effect_scope)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/30-basic-reactivity-system/050-other-apis.md",
    "content": "# 其他響應式 API\n\n::: warning\n這裡解釋的實現基於當前草擬的[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)之前的版本．  \n一旦[響應式優化](/zh-tw/30-basic-reactivity-system/005-reactivity-optimization)完成，本章的內容將更新以與其保持一致．\n:::\n\n## 讓我們實現其他響應式 API！\n\n- customRef\n- readonly\n- shallowReactive\n- unref\n- isProxy\n- isReactive\n- isReadonly\n\n如果你已經走到這一步，你應該能夠透過閱讀原始碼來實現它們，而無需任何解釋！\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/30_basic_reactivity_system/150_other_apis)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/40-basic-component-system/010-lifecycle-hooks.md",
    "content": "# 生命週期鉤子（基礎組件系統開始）\n\n## 讓我們實現生命週期鉤子\n\n實現生命週期鉤子非常簡單．\n你只需要在 ComponentInternalInstance 中註冊函式，並在渲染期間的指定時機執行它們．\nAPI 本身將在 runtime-core/apiLifecycle.ts 中實現．\n\n需要注意的一點是，你需要考慮 onMounted/onUnmounted/onUpdated 的調度．\n註冊的函式應該在掛載，卸載和更新完全完成後執行．\n\n因此，我們將在調度器中實現一種名為\"post\"的新佇列類型．這是在現有佇列刷新完成後才會被刷新的佇列．\n圖像 ↓\n\n```ts\nconst queue: SchedulerJob[] = [] // 現有實現\nconst pendingPostFlushCbs: SchedulerJob[] = [] // 這次要創建的新佇列\n\nfunction queueFlush() {\n  queue.forEach(job => job())\n  flushPostFlushCbs() // 在佇列刷新後刷新\n}\n```\n\n同時，透過這個，讓我們實現一個入佇列到 pendingPostFlushCbs 的 API．\n並且讓我們使用它將渲染器中的 effect 入佇列到 pendingPostFlushCbs．\n\n這次要支援的生命週期鉤子：\n\n- onMounted\n- onUpdated\n- onUnmounted\n- onBeforeMount\n- onBeforeUpdate\n- onBeforeUnmount\n\n讓我們實現它，目標是使以下程式碼工作！\n\n```ts\nimport {\n  createApp,\n  h,\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n  ref,\n} from 'chibivue'\n\nconst Child = {\n  setup() {\n    const count = ref(0)\n    onBeforeMount(() => {\n      console.log('onBeforeMount')\n    })\n\n    onUnmounted(() => {\n      console.log('onUnmounted')\n    })\n\n    onBeforeUnmount(() => {\n      console.log('onBeforeUnmount')\n    })\n\n    onBeforeUpdate(() => {\n      console.log('onBeforeUpdate')\n    })\n\n    onUpdated(() => {\n      console.log('onUpdated')\n    })\n\n    onMounted(() => {\n      console.log('onMounted')\n    })\n\n    return () =>\n      h('div', {}, [\n        h('p', {}, [`${count.value}`]),\n        h('button', { onClick: () => count.value++ }, ['increment']),\n      ])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const mountFlag = ref(true)\n\n    return () =>\n      h('div', {}, [\n        h('button', { onClick: () => (mountFlag.value = !mountFlag.value) }, [\n          'toggle',\n        ]),\n        mountFlag.value ? h(Child, {}, []) : h('p', {}, ['unmounted']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/010_lifecycle_hooks)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/40-basic-component-system/020-provide-inject.md",
    "content": "# Provide/Inject 的實現\n\n## 讓我們實現 Provide/Inject\n\n這是 Provide 和 Inject 的實現．實現也相當簡單．  \n基本概念是在 ComponentInternalInstance 中有一個地方來儲存提供的資料（provides），並保持父組件的實例來繼承資料．\n\n需要注意的一點是，provide 有兩個入口點．一個是在組件的 setup 期間，這很容易想像，  \n另一個是在 App 上呼叫 provide 時．\n\n```ts\nconst app = createApp({\n  setup() {\n    //.\n    //.\n    //.\n    provide('key', someValue) // 這是從組件呼叫 provide 的情況\n    //.\n    //.\n  },\n})\n\napp.provide('key2', someValue2) // 在 App 上提供\n```\n\n現在，我們應該在哪裡儲存 app 提供的內容？由於 app 不是組件，這是一個問題．\n\n為了給你答案，讓我們說 app 實例有一個名為 AppContext 的物件，我們將在其中儲存 provides 物件．\n\n將來，我們將向這個 AppContext 添加全域組件和自訂指令設定．\n\n現在我們已經解釋了到目前為止的一切，讓我們實現程式碼，使其按以下方式工作！\n\n※ 假設的簽名\n\n```ts\nexport interface InjectionKey<_T> extends Symbol {}\n\nexport function provide<T, K = InjectionKey<T> | string | number>(\n  key: K,\n  value: K extends InjectionKey<infer V> ? V : T,\n)\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T\n```\n\n```ts\nconst Child = {\n  setup() {\n    const rootState = inject<{ count: number }>('RootState')\n    const logger = inject(LoggerKey)\n\n    const action = () => {\n      rootState && rootState.count++\n      logger?.('Hello from Child.')\n    }\n\n    return () => h('button', { onClick: action }, ['action'])\n  },\n}\n\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 1 })\n    provide('RootState', state)\n\n    return () =>\n      h('div', {}, [h('p', {}, [`${state.count}`]), h(Child, {}, [])])\n  },\n})\n\ntype Logger = (...args: any) => void\nconst LoggerKey = Symbol() as InjectionKey<Logger>\n\napp.provide(LoggerKey, window.console.log)\n```\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/020_provide_inject)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/40-basic-component-system/030-component-proxy-setup-context.md",
    "content": "# 組件的代理和 setupContext\n\n## 組件的代理\n\n組件有一個重要概念叫做代理（Proxy）．  \n簡單來說，它是一個允許存取組件實例資料（公共屬性）的代理．\n代理結合了 setup 的結果（狀態，函式），data，props 和其他存取．\n\n讓我們考慮以下程式碼（包括在 chibivue 中未實現的部分，所以請將其視為常規 Vue）：\n\n```vue\n<script>\nexport default defineComponent({\n  props: { parentCount: { type: Number, default: 0 } },\n  data() {\n    return { dataState: { count: 0 } }\n  },\n  methods: {\n    incrementData() {\n      this.dataState.count++\n    },\n  },\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => {\n      state.count++\n    }\n\n    return { state, increment }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <p>count (parent): {{ parentCount }}</p>\n\n    <br />\n\n    <p>count (data): {{ dataState.count }}</p>\n    <button @click=\"incrementData\">increment (data)</button>\n\n    <br />\n\n    <p>count: {{ state.count }}</p>\n    <button @click=\"increment\">increment</button>\n  </div>\n</template>\n```\n\n這段程式碼工作正常，但它是如何綁定到模板的？\n\n讓我們考慮另一個例子．\n\n```vue\n<script setup>\nconst ChildRef = ref()\n\n// 存取組件的方法和資料\n// ChildRef.value?.incrementData\n// ChildRef.value?.increment\n</script>\n\n<template>\n  <!-- Child 是前面提到的組件 -->\n  <Child :ref=\"ChildRef\" />\n</template>\n```\n\n在這種情況下，你可以透過 ref 存取組件的資訊．\n\n為了實現這一點，ComponentInternalInstance 有一個名為 proxy 的屬性，它保存用於資料存取的代理．\n\n換句話說，模板（渲染函式）和 ref 引用 instance.proxy．\n\n```ts\ninterface ComponentInternalInstance {\n  proxy: ComponentPublicInstance | null\n}\n```\n\n這個代理的實現是使用 Proxy 完成的，大致如下：\n\n```ts\ninstance.proxy = instance.proxy = new Proxy(\n  instance,\n  PublicInstanceProxyHandlers,\n)\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get(instance: ComponentRenderContext, key: string) {\n    const { setupState, ctx, props } = instance\n\n    // 根據鍵按 setupState -> props -> ctx 的順序檢查，如果存在則返回值\n  },\n}\n```\n\n讓我們實現這個代理！\n\n一旦實現，讓我們修改程式碼以將此代理傳遞給渲染函式和 ref．\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/030_component_proxy)\n\n※ 順便說一下，我還實現了 defineComponent 和相關類型檢查的實現（這允許我們推斷代理資料的類型）．\n\n![Component type inference result](/figures/40-basic-component-system/component-proxy-setup-context/infer-component-types.png)\n\n## setupContext\n\nhttps://ja.vuejs.org/api/composition-api-setup.html#setup-context\n\nVue 有一個名為 setupContext 的概念．這是在 setup 函式中暴露的上下文，包括 emit 和 expose．\n\n目前，emit 正在工作，但實現有些粗糙．\n\n```ts\nconst setupResult = component.setup(instance.props, {\n  emit: instance.emit,\n})\n```\n\n讓我們正確定義 SetupContext 介面，並將其表示為實例持有的物件．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  setupContext: SetupContext | null // 添加\n}\n\nexport type SetupContext = {\n  emit: (e: string, ...args: any[]) => void\n}\n```\n\n然後，在創建實例時，生成 setupContext 並在執行 setup 函式時將此物件作為第二個參數傳遞．\n\n## expose\n\n一旦你到達這一點，讓我們嘗試實現除 emit 之外的 SetupContext．  \n作為這次的例子，讓我們實現 expose．\n\nexpose 是一個允許你明確定義公共屬性的函式．  \n讓我們目標實現如下的開發者介面：\n\n```ts\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>hello</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const log = () => {\n      console.log(\n        child.value.count,\n        child.value.count2, // 無法存取\n        child2.value.count,\n        child2.value.count2,\n      )\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: log }, ['log']),\n      ])\n  },\n})\n```\n\n對於不使用 expose 的組件，預設情況下一切仍然是公共的．\n\n作為方向，讓我們在實例內部有一個名為 `exposed` 的物件，如果這裡設置了值，我們將把這個物件傳遞給 templateRef 的 ref．\n\n```ts\nexport interface ComponentInternalInstance {\n  // .\n  // .\n  // .\n  exposed: Record<string, any> | null // 添加\n}\n```\n\n讓我們實現 expose 函式，以便可以在這裡註冊物件．\n\n## ProxyRefs\n\n在本章中，我們實現了 proxy 和 exposedProxy，但實際上與原始 Vue 有一些差異．\n那就是\"ref 被解包\"．（在 proxy 的情況下，setupState 具有此屬性而不是 proxy．）\n\n這些是用 ProxyRefs 實現的，處理器在名為 `shallowUnwrapHandlers` 的名稱下實現．\n這允許我們在編寫模板或處理代理時消除 ref 特定值的冗餘．\n\n```ts\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key]\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value\n      return true\n    } else {\n      return Reflect.set(target, key, value, receiver)\n    }\n  },\n}\n```\n\n```vue\n<template>\n  <!-- <p>{{ count.value }}</p>  不需要這樣寫 -->\n  <p>{{ count }}</p>\n</template>\n```\n\n如果你實現到這一點，以下程式碼應該工作．\n\n```ts\nimport { createApp, defineComponent, h, ref } from 'chibivue'\n\nconst Child = defineComponent({\n  setup(_, { expose }) {\n    const count = ref(0)\n    const count2 = ref(0)\n    expose({ count })\n    return { count, count2 }\n  },\n  template: `<p>child {{ count }} {{ count2 }}</p>`,\n})\n\nconst Child2 = defineComponent({\n  setup() {\n    const count = ref(0)\n    const count2 = ref(0)\n    return { count, count2 }\n  },\n  template: `<p>child2 {{ count }} {{ count2 }}</p>`,\n})\n\nconst app = createApp({\n  setup() {\n    const child = ref()\n    const child2 = ref()\n\n    const increment = () => {\n      child.value.count++\n      child.value.count2++ // 無法存取\n      child2.value.count++\n      child2.value.count2++\n    }\n\n    return () =>\n      h('div', {}, [\n        h(Child, { ref: child }, []),\n        h(Child2, { ref: child2 }, []),\n        h('button', { onClick: increment }, ['increment']),\n      ])\n  },\n})\n\napp.mount('#app')\n```\n\n## 模板綁定和 with 語句\n\n實際上，本章的更改存在一個問題．\n讓我們嘗試執行以下程式碼：\n\n```ts\nconst Child2 = {\n  setup() {\n    const state = reactive({ count: 0 })\n    return { state }\n  },\n  template: `<p>child2 count: {{ state.count }}</p>`,\n}\n```\n\n這只是一個簡單的程式碼，但它不工作．\n它抱怨 state 未定義．\n\n![state is not defined runtime error](/figures/40-basic-component-system/component-proxy-setup-context/state-is-not-defined.png)\n\n原因是當將代理作為參數傳遞給 with 語句時，必須定義 has．\n\n[使用 with 語句和代理創建動態命名空間 (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with#creating_dynamic_namespaces_using_the_with_statement_and_a_proxy)\n\n所以讓我們在 PublicInstanceProxyHandlers 中實現 has．\n如果鍵存在於 setupState，props 或 ctx 中，它應該返回 true．\n\n```ts\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  // .\n  // .\n  // .\n  has(\n    { _: { setupState, ctx, propsOptions } }: ComponentRenderContext,\n    key: string,\n  ) {\n    let normalizedProps\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    )\n  },\n}\n```\n\n如果它工作正常，應該可以正常工作！\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/040_setup_context)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/40-basic-component-system/040-component-slot.md",
    "content": "# 插槽\n\n## 預設插槽的實現\n\nVue 有一個名為插槽的功能，包括三種類型：預設插槽，命名插槽和作用域插槽．\nhttps://vuejs.org/guide/components/slots.html#slots\n\n這次，我們將實現其中的預設插槽．\n期望的開發者介面如下：\n\nhttps://vuejs.org/guide/extras/render-function.html#passing-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () => h('div', {}, [slots.default()])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () => h(MyComponent, {}, () => 'hello')\n  },\n})\n```\n\n機制很簡單．在插槽定義端，我們確保將 slots 作為 setupContext 接收，在使用端用 h 函式渲染組件時，我們只需將渲染函式作為 children 傳遞．\n也許對每個人來說最熟悉的用法是在 SFC 的模板中放置一個 slot 元素，但這需要實現一個單獨的模板編譯器，所以我們這次省略它．（我們將在基礎模板編譯器部分介紹它．）\n\n像往常一樣，向實例添加一個可以保存插槽的屬性，並使用 createSetupContext 將其作為 SetupContext 混合．\n修改 h 函式，使其可以接收渲染函式作為第三個參數，而不僅僅是陣列，如果傳遞了渲染函式，在生成實例時將其設置為組件實例的預設插槽．\n讓我們先實現到這一點！\n\n（由於在 children 中實現了 normalize，ShapeFlags 已經略有更改．）\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/050_component_slot)\n\n## 命名插槽/作用域插槽的實現\n\n這是預設插槽的擴展．\n這次，讓我們嘗試傳遞一個物件而不是函式．\n\n對於作用域插槽，你只需要定義渲染函式的參數．\n如你所見，當使用渲染函式時，似乎沒有必要區分作用域插槽．\n沒錯，插槽的本質只是一個回呼函式，API 作為作用域插槽提供以允許向其傳遞參數．\n當然，我們將在基礎模板編譯器部分實現一個可以處理作用域插槽的編譯器，但它們將被轉換為這些形式．\n\nhttps://vuejs.org/guide/components/slots.html#scoped-slots\n\n```ts\nconst MyComponent = defineComponent({\n  setup(_, { slots }) {\n    return () =>\n      h('div', {}, [\n        slots.default?.(),\n        h('br', {}, []),\n        slots.myNamedSlot?.(),\n        h('br', {}, []),\n        slots.myScopedSlot2?.({ message: 'hello!' }),\n      ])\n  },\n})\n\nconst app = createApp({\n  setup() {\n    return () =>\n      h(\n        MyComponent,\n        {},\n        {\n          default: () => 'hello',\n          myNamedSlot: () => 'hello2',\n          myScopedSlot2: (scope: { message: string }) =>\n            `message: ${scope.message}`,\n        },\n      )\n  },\n})\n```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/060_slot_extend)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/40-basic-component-system/050-options-api.md",
    "content": "# 支援 Options API\n\n## Options API\n\n到目前為止，我們已經能夠使用 Composition API 實現很多功能，但讓我們也支援 Options API．\n\n在本書中，我們在 Options API 中支援以下內容：\n\n- props\n- data\n- computed\n- method\n- watch\n- slot\n- lifecycle\n  - onMounted\n  - onUpdated\n  - onUnmounted\n  - onBeforeMount\n  - onBeforeUpdate\n  - onBeforeUnmount\n- provide/inject\n- $el\n- $data\n- $props\n- $slots\n- $parent\n- $emit\n- $forceUpdate\n- $nextTick\n\n作為實現方法，讓我們在 \"componentOptions.ts\" 中準備一個名為 \"applyOptions\" 的函式，並在 \"setupComponent\" 的末尾執行它．\n\n```ts\nexport const setupComponent = (instance: ComponentInternalInstance) => {\n  // .\n  // .\n  // .\n\n  if (render) {\n    instance.render = render as InternalRenderFunction\n  }\n  // ↑ 這是現有實現\n\n  setCurrentInstance(instance)\n  applyOptions(instance)\n  unsetCurrentInstance()\n}\n```\n\n在 Options API 中，開發者介面經常處理 \"this\"．\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { message: 'hello' }\n  },\n\n  methods: {\n    greet() {\n      console.log(this.message) // 像這樣\n    },\n  },\n})\n```\n\n在內部，\"this\" 在 Options API 中指向組件的代理，在應用選項時，這個代理被綁定．\n\n實現示例 ↓\n\n```ts\nexport function applyOptions(instance: ComponentInternalInstance) {\n  const { type: options } = instance\n  const publicThis = instance.proxy! as any\n  const ctx = instance.ctx\n\n  const { methods } = options\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key]\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis)\n      }\n    }\n  }\n}\n```\n\n基本上，如果你使用這個原理逐一實現它們，應該不會太困難．\n\n如果你想讓 \"data\" 變成響應式，在這裡呼叫 \"reactive\" 函式，如果你想計算，在這裡呼叫 \"computed\" 函式．（\"provide/inject\" 也是如此）\n\n由於在執行 \"applyOptions\" 之前透過 \"setCurrentInstance\" 設置了實例，你可以以相同的方式呼叫到目前為止一直使用的 API（Composition API）．\n\n關於以 \"$\" 開頭的屬性，它們由 \"componentPublicInstance\" 的實現控制．\"PublicInstanceProxyHandlers\" 中的 getter 控制它們．\n\n## 為 Options API 添加類型\n\n在功能上，按照上述描述實現是可以的，但為 Options API 添加類型有點複雜．\n\n在本書中，我們為 Options API 支援基本類型．\n\n困難的地方在於 \"this\" 的類型根據使用者對每個選項的定義而改變．例如，如果你在 \"data\" 選項中定義一個名為 \"count\" 的 \"number\" 類型屬性，你希望 \"computed\" 或 \"method\" 中的 \"this\" 推斷出 \"count: number\"．\n\n當然，這不僅適用於 \"data\"，也適用於在 \"computed\" 或 \"methods\" 中定義的那些．\n\n```ts\nconst App = defineComponent({\n  data() {\n    return { count: 0 }\n  },\n\n  methods: {\n    myMethod() {\n      this.count // number\n      this.myComputed // number\n    },\n  },\n\n  computed: {\n    myComputed() {\n      return this.count // number\n    },\n  },\n})\n```\n\n為了實現這一點，你需要實現一個有些複雜的類型拼圖（具有許多泛型的中繼）．\n\n從 \"defineComponent\" 的類型開始，我們實現幾種類型來中繼到 \"ComponentOptions\" 和 \"ComponentPublicInstance\"．\n\n在這裡，讓我們專注於 \"data\" 和 \"methods\" 進行解釋．\n\n首先，通常的 \"ComponentOptions\" 類型．我們用泛型擴展它以接受 \"data\" 和 \"methods\" 的類型作為參數，\"D\" 和 \"M\"．\n\n```ts\nexport type ComponentOptions<\n  D = {},\n  M extends MethodOptions = MethodOptions\n> = {\n  data?: () => D;,\n  methods?: M;\n};\n\ninterface MethodOptions {\n  [key: string]: Function;\n}\n```\n\n到目前為止，應該不會太困難．這是可以應用於 \"defineComponent\" 參數的類型．  \n當然，在 \"defineComponent\" 中，我們也接受 \"D\" 和 \"M\" 來中繼使用者定義的類型．這允許我們中繼使用者定義的類型．\n\n```ts\nexport function defineComponent<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n>(options: ComponentOptions<D, M>) {}\n```\n\n問題是在 \"methods\" 中處理 \"this\" 時如何將 \"D\" 與 \"this\" 混合（如何使 \"this.count\" 這樣的推斷成為可能）．\n\n首先，\"D\" 和 \"M\" 被合併到 \"ComponentPublicInstance\" 中（合併到代理中）．這可以理解如下（用泛型擴展）．\n\n```ts\ntype ComponentPublicInstance<\n  D = {},\n  M extends MethodOptions = MethodOptions,\n> = {\n  /** 公共實例具有的各種類型 */\n} & D &\n  M\n```\n\n一旦我們有了這個，我們就將實例類型混合到 \"ComponentOptions\" 中的 \"this\" 中．\n\n```ts\ntype ComponentOptions<D = {}, M extends MethodOptions = MethodOptions> = {\n  data?: () => D\n  methods?: M\n} & ThisType<ComponentPublicInstance<D, M>>\n```\n\n透過這樣做，我們可以從選項中的 \"this\" 推斷在 \"data\" 和 \"method\" 中定義的屬性．\n\n在實踐中，我們需要推斷各種類型，如 \"props\"，\"computed\" 和 \"inject\"，但基本原理是相同的．  \n乍一看，你可能會被許多泛型和類型轉換（如從 \"inject\" 中僅提取 \"key\"）所壓倒，但如果你冷靜下來並回到原理，你應該沒問題．  \n在本書的程式碼中，受原始 Vue 的啟發，我們用 \"CreateComponentPublicInstance\" 進一步抽象了一步，並實現了一個名為 \"ComponentPublicInstanceConstructor\" 的類型，但不要太擔心．（如果你感興趣，你也可以閱讀它！）\n\n到此為止的原始碼：  \n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/40_basic_component_system/070_options_api)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/010-transform.md",
    "content": "# 轉換器和程式碼生成重構的實現（基礎模板編譯器部門開始）\n\n## 現有實現的回顧\n\n現在，讓我們從最小示例部門停下的地方開始更認真地實現模板編譯器．距離我們上次處理它已經有一段時間了，所以讓我們回顧一下當前的實現．主要關鍵詞是 Parse，AST 和 Codegen．\n\n![Minimum compiler pipeline](/figures/50-basic-template-compiler/transform/basic-compiler-pipeline.svg)\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  const code = generate(ast, option)\n  return code\n}\n```\n\n實際上，這個配置與原始配置略有不同．讓我們看看原始程式碼．\n\nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/compile.ts#L61\n\n你能理解嗎...？\n\n```ts\nexport function baseCompile(\n  template: string,\n  option: Required<CompilerOptions>,\n) {\n  const ast = baseParse(template.trim())\n  transform(ast)\n  const code = generate(ast, option)\n  return code\n}\n```\n\n就是這樣．\n\n這次，我們將實現 `transform` 函式．\n\n![Compiler pipeline with transformer](/figures/50-basic-template-compiler/transform/compiler-pipeline-with-transformer.svg)\n\n## 什麼是 Transform？\n\n正如你從上面的程式碼中可以想像的那樣，透過解析獲得的 AST 被 `transform` 函式以某種方式轉換．\n\n你可以透過閱讀這個來了解．  \nhttps://github.com/vuejs/core/blob/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/ast.ts#L43C1-L51C23\n\n這個 VNODE_CALL 和以 JS 開頭的名稱的 AST 程式碼是我們這次要處理的．\nVue.js 的模板編譯器分為兩部分：表示解析模板結果的 AST 和表示生成程式碼的 AST．\n我們當前的實現只處理前一個 AST．\n\n讓我們考慮將模板 `<p>hello</p>` 作為輸入的情況．\n\n首先，透過解析生成以下 AST．這與現有實現相同．\n\n```ts\ninterface ElementNode {\n  tag: string\n  props: object /** 省略 */\n  children: (ElementNode | TextNode | InterpolationNode)[]\n}\n\ninterface TextNode {\n  content: string\n}\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {},\n  \"children\": [{ \"content\": \"hello\" }]\n}\n```\n\n至於\"表示生成程式碼的 AST\"，讓我們考慮應該生成什麼樣的程式碼．\n我認為應該是這樣的：\n\n```ts\nh('p', {}, ['hello'])\n```\n\n這是表示生成的 JavaScript 程式碼的 AST．\n換句話說，它是一個表示用於生成應該生成的程式碼的 AST 的物件．\n\n```ts\ninterface VNodeCall {\n  tag: string\n  props: PropsExpression\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode // single text child\n    | undefined\n}\n\ntype PropsExpression = ObjectExpression | CallExpression | ExpressionNode\ntype TemplateChildNode = ElementNode | InterpolationNode | TextNode\n```\n\n```json\n{\n  \"tag\": \"p\",\n  \"props\": {\n    \"type\": \"ObjectExpression\",\n    \"properties\": []\n  },\n  \"children\": { \"content\": \"hello\" }\n}\n```\n\n透過這種方式，表示由 Codegen 生成的程式碼的 AST 被表達．\n你可能在這一點上感覺不到分離它們的必要性，但在將來實現指令時會很有用．\n透過分離專注於輸入的 AST 和專注於輸出的 AST，我們可以使用稱為 `transform` 的函式執行從 `input AST -> output AST` 的轉換．\n\n## Codegen 節點\n\n現在我們已經掌握了流程，讓我們確認我們將處理什麼樣的節點（我們想要轉換什麼樣的節點）．我將在枚舉它們並提供註釋的同時進行解釋．請參考原始碼獲取準確資訊，因為某些部分被省略了．\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[]\n}\n\n// 這表示呼叫 h 函式的表達式。\n// 它假設類似 `h(\"p\", { class: 'message'}, [\"hello\"])` 的東西。\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: ObjectExpression | undefined // 注意：在原始碼中實現為 PropsExpression（用於未來擴展）\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode\n\n// 這表示一個 JavaScript 物件。它用於 VNodeCall 的 props 等。\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION\n  properties: Array<Property>\n}\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY\n  key: ExpressionNode\n  value: JSChildNode\n}\n\n// 這表示一個 JavaScript 陣列。它用於 VNodeCall 的 children 等。\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION\n  elements: Array<string | Node>\n}\n```\n\n## 轉換器設計\n\n在實現轉換器之前，讓我們談談設計．首先，重要的是要注意有兩種類型的轉換器：NodeTransform 和 DirectiveTransform．這些分別用於轉換節點和指令，並採用以下介面．\n\n```ts\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[]\n\n// TODO:\n// export type DirectiveTransform = (\n//   dir: DirectiveNode,\n//   node: ElementNode,\n//   context: TransformContext,\n// ) => DirectiveTransformResult;\nexport type DirectiveTransform = Function\n```\n\nDirectiveTransform 將在實現指令時在後面的章節中介紹，所以現在讓我們稱之為 Function．\nNodeTransform 和 DirectiveTransform 實際上都是函式．你可以將它們視為轉換 AST 的函式．\n請注意，NodeTransform 的結果是一個函式．在實現 transform 時，如果你實現它返回一個函式，該函式將在該節點的轉換之後執行（它被稱為 onExit 過程）．\n你想在節點的 transform 之後執行的任何處理都應該在這裡描述．我將在稍後描述稱為 traverseNode 的函式時解釋這一點．\n介面的解釋主要如上所述．\n\n作為更具體的實現，有用於轉換元素的 transformElement 和用於轉換表達式的 transformExpression 等．\n至於 DirectiveTransform 的實現，每個指令都有實現．\n這些實現在 compiler-core/src/transforms 中實現．具體的轉換過程在這裡實現．\n\nhttps://github.com/vuejs/core/tree/37a14a5dae9999bbe684c6de400afc63658ffe90/packages/compiler-core/src/transforms\n\n圖像 ↓\n\n![Transform type relationships](/figures/50-basic-template-compiler/transform/transform-type-relationships.svg)\n\n接下來，關於上下文，TransformContext 保存在這些轉換期間使用的資訊和函式．\n將來會添加更多，但現在這就足夠了．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n}\n```\n\n## 轉換器的實現\n\n現在讓我們在實踐中看看 transform 函式．首先，讓我們從獨立於每個轉換過程內容的框架的一般解釋開始．\n\n結構非常簡單，只需生成上下文並使用 traverseNode 函式遍歷節點．\n這個 traverseNode 函式是轉換的主要實現．\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n}\n```\n\n在 traverseNode 中，基本上，它只是將保存在上下文中的 nodeTransforms（轉換節點的函式集合）應用於節點．\n對於那些有子節點的，子節點也透過 traverseNode 傳遞．\n在介面解釋期間提到的 onExit 的實現也在這裡．\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  context.currentNode = node\n\n  const { nodeTransforms } = context\n  const exitFns = [] // 轉換後要執行的操作\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context)\n\n    // 註冊轉換後要執行的操作\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit)\n      } else {\n        exitFns.push(onExit)\n      }\n    }\n    if (!context.currentNode) {\n      return\n    } else {\n      node = context.currentNode\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.INTERPOLATION:\n      break\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n\n  context.currentNode = node\n\n  // 執行轉換後要執行的操作\n  let i = exitFns.length\n  while (i--) {\n    exitFns[i]() // 可以假設轉換已完成而執行的操作\n  }\n}\n\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    traverseNode(child, context)\n  }\n}\n```\n\n接下來，讓我們談談具體的轉換過程．作為示例，讓我們實現 transformElement．\n\n在 transformElement 中，我們主要將類型為 NodeTypes.ELEMENT 的節點轉換為 VNodeCall．\n\n```ts\nexport interface ElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n  isSelfClosing: boolean\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\n// ↓↓↓↓↓↓ 轉換 ↓↓↓↓↓↓ //\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[] // multiple children\n    | TemplateTextChildNode\n    | undefined\n}\n```\n\n這是一個簡單的物件到物件的轉換，所以我認為不會很困難．讓我們嘗試透過閱讀原始碼來實現它．\n我將貼上我這次假設的程式碼以防萬一．（指令支援將在另一章中完成．）\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if (node.type !== NodeTypes.ELEMENT) return\n\n    const { tag, props } = node\n\n    const vnodeTag = `\"${tag}\"`\n    let vnodeProps: VNodeCall['props']\n    let vnodeChildren: VNodeCall['children']\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node)\n      vnodeProps = propsBuildResult.props\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0]\n        const type = child.type\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode\n        } else {\n          vnodeChildren = node.children\n        }\n      } else {\n        vnodeChildren = node.children\n      }\n    }\n\n    node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, vnodeChildren)\n  }\n}\n\nexport function buildProps(node: ElementNode): {\n  props: PropsExpression | undefined\n  directives: DirectiveNode[]\n} {\n  const { props } = node\n  let properties: ObjectExpression['properties'] = []\n  const runtimeDirectives: DirectiveNode[] = []\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i]\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop\n\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : '', true),\n        ),\n      )\n    } else {\n      // directives\n      // TODO:\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined\n  if (properties.length) {\n    propsExpression = createObjectExpression(properties)\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  }\n}\n```\n\n## 基於轉換後的 AST 的程式碼生成\n\n由於我們為 Codegen 轉換了 AST，我們也需要支援 Codegen．\n對於進入 Codegen 的 AST，假設 VNodeClass（以及它們擁有的節點）編寫程式碼就足夠了．\n期望的最終字串表示與以前相同．\n\n現有的 Codegen 實現非常簡單，所以讓我們在這裡使它更正式一些（因為它相當硬編碼）．\n讓我們也創建一個 Codegen 特定的上下文並將生成的程式碼推送到其中．\n此外，讓我們在上下文中實現一些輔助函式（如縮排）．\n\n```ts\nexport interface CodegenContext {\n  source: string\n  code: string\n  indentLevel: number\n  line: 1\n  column: 1\n  offset: 0\n  push(code: string, node?: CodegenNode): void\n  indent(): void\n  deindent(withoutNewLine?: boolean): void\n  newline(): void\n}\n```\n\n我將在這裡省略實現細節，但我只是為每個角色分離了函式，實現方法沒有重大變化．\n由於我還沒有能夠支援指令，由於在該區域刪除了臨時實現，有些部分不工作，但\n如果程式碼大致按以下方式工作，就可以了！\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> Hello World! </p>\n      <p> Count: {{ count }} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/010_transformer)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/020-v-bind.md",
    "content": "# 讓我們實現指令（v-bind）\n\n## 方法\n\n現在讓我們實現指令，這是 Vue.js 的精髓．  \n像往常一樣，我們將指令應用到轉換器，出現在那裡的介面稱為 DirectiveTransform．  \nDirectiveTransform 接受 DirectiveNode 和 ElementNode 作為參數，並返回轉換後的 Property．\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n) => DirectiveTransformResult\n\nexport interface DirectiveTransformResult {\n  props: Property[]\n}\n```\n\n首先，讓我們檢查這次我們要實現的開發者介面．\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const bind = { id: 'some-id', class: 'some-class', style: 'color: red' }\n    return { count: 1, bind }\n  },\n\n  template: `<div>\n  <p v-bind:id=\"count\"> v-bind:id=\"count\" </p>\n  <p :id=\"count * 2\"> :id=\"count * 2\" </p>\n\n  <p v-bind:[\"style\"]=\"bind.style\"> v-bind:[\"style\"]=\"bind.style\" </p>\n  <p :[\"style\"]=\"bind.style\"> :[\"style\"]=\"bind.style\" </p>\n\n  <p v-bind=\"bind\"> v-bind=\"bind\" </p>\n\n  <p :style=\"{ 'font-weight': 'bold' }\"> :style=\"{ font-weight: 'bold' }\" </p>\n  <p :style=\"'font-weight: bold;'\"> :style=\"'font-weight: bold;'\" </p>\n\n  <p :class=\"'my-class my-class2'\"> :class=\"'my-class my-class2'\" </p>\n  <p :class=\"['my-class']\"> :class=\"['my-class']\" </p>\n  <p :class=\"{ 'my-class': true }\"> :class=\"{ 'my-class': true }\" </p>\n  <p :class=\"{ 'my-class': false }\"> :class=\"{ 'my-class': false }\" </p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\nv-bind 有各種表示法．詳情請參考官方文件．  \n我們還將處理 class 和 style．\n\nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\n## AST 修改\n\n首先，讓我們修改 AST．目前，exp 和 arg 都是簡單的字串，所以我們需要將它們更改為接受 ExpressionNode．\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined // 這裡\n  arg: ExpressionNode | undefined // 這裡\n}\n```\n\n讓我再次解釋 name，arg 和 exp．  \nname 是指令名稱，如 v-bind 或 v-on．它可以是 on 或 bind．  \n由於我們這次實現 v-bind，它將是 bind．\n\narg 是由 : 指定的參數．對於 v-bind，它包括 id 和 style．  \n（在 v-on 的情況下，它包括 click 和 input．）\n\nexp 是右側．在 v-bind:id=\"count\" 的情況下，包含 count．  \nexp 和 arg 都可以動態嵌入變數，所以它們的類型是 ExpressionNode．  \n（因為 arg 也可以像 v-bind:[key]=\"count\" 一樣是動態的）\n\n![DirectiveNode shape for v-bind](/figures/50-basic-template-compiler/v-bind/directive-node-shape.svg)\n\n## 解析器修改\n\n我們將更新解析器實現以遵循這個 AST 修改．我們將 exp 和 arg 解析為 SimpleExpressionNode．\n\n我們還將解析 v-on 中使用的 @ 和插槽中使用的 #．  \n（由於考慮正規表達式很麻煩（而且在解釋時逐漸添加它們很麻煩），我們現在將借用原始程式碼．）  \n參考：https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/parse.ts#L802\n\n由於程式碼有點長，我將在程式碼中寫註釋來解釋．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  // .\n  // directive\n  const loc = getSelection(context, start)\n  // 這裡的正規表達式是從原始原始碼借用的\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match =\n      // 這裡的正規表達式是從原始原始碼借用的\n      /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(\n        name,\n      )!\n\n    // 檢查名稱部分的匹配，如果以 \":\" 開頭則將其視為 \"bind\"\n    let dirName =\n      match[1] ||\n      (startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : '')\n\n    let arg: ExpressionNode | undefined\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2])\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      )\n\n      let content = match[2]\n      let isStatic = true\n\n      // 如果是像 \"[arg]\" 這樣的動態參數，將 isStatic 設置為 false 並提取內容作為內容\n      if (content.startsWith('[')) {\n        isStatic = false\n        if (!content.endsWith(']')) {\n          console.error(`Invalid dynamic argument expression: ${content}`)\n          content = content.slice(1)\n        } else {\n          content = content.slice(1, content.length - 1)\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      }\n    }\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      loc,\n      arg,\n    }\n  }\n}\n```\n\n透過這樣，我們能夠解析這次想要處理的 AST Node．\n\n## 轉換器的實現\n\n接下來，讓我們編寫將此 AST 轉換為 Codegen AST 的實現．  \n由於它有點複雜，我在下圖中總結了流程．請先看一下．  \n一般來說，必要的項目是 v-bind 是否有參數，是否是 class 或 style．  \n※ 省略了這次不涉及的處理部分．（請注意這個圖不是很嚴格．）\n\n![v-bind transform flow](/figures/50-basic-template-compiler/v-bind/transform-vbind-flow.svg)\n\n首先，作為前提，由於指令基本上是為元素宣告的，  \n與指令相關的轉換器從 transformElement 呼叫．\n\n由於我們這次想要實現 v-bind，我們將實現一個名為 transformVBind 的函式，  \n但需要注意的一點是，這個函式只轉換具有 args 的宣告．\n\ntransformVBind 的作用是將\n\n```\nv-bind:id=\"count\"\n```\n\n轉換為像這樣的物件（實際上是表示此物件的 Codegen Node）\n\n```ts\n{\n  id: count\n}\n```\n\n在原始實現中也給出了以下解釋．\n\n> codegen for the entire props object. This transform here is only for v-bind _with_ args.\n\n引用自：https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/compiler-core/src/transforms/vBind.ts#L13C1-L14C16\n\n正如你從流程中可以看到的，transformElement 檢查指令的 arg，如果它不存在，它不執行 transformVBind，而是將其轉換為對 mergeProps 的函式呼叫．\n\n```vue\n<p v-bind=\"bindingObject\" class=\"my-class\">hello</p>\n```\n\n↓\n\n```ts\nh('p', mergeProps(bindingObject, { class: 'my-class' }), 'hello')\n```\n\n另外，對於 class 和 style，它們有各種開發者介面，所以需要進行規範化．  \nhttps://vuejs.org/api/built-in-directives.html#v-bind\n\n實現名為 normalizeClass 和 normalizeStyle 的函式，並分別應用它們．\n\n如果 arg 是動態的，無法確定具體的，所以實現一個名為 normalizeProps 的函式並呼叫它．（它在內部呼叫 normalizeClass 和 normalizeStyle）\n\n現在我們已經實現到這裡，讓我們看看它是如何工作的！\n\n![v-bind test result in the browser](/figures/50-basic-template-compiler/v-bind/vbind-test-result.png)\n\n看起來很棒！\n\n下次，我們將實現 v-on．\n\n到此為止的原始碼：  \n[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/020_v_bind)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/022-transform-expression.md",
    "content": "# transformExpression\n\n## 要實現的開發者介面和當前挑戰\n\n首先，看看這個組件．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <button :onClick=\"increment\">count + count is: {{ count + count }}</button>\n  </div>\n</template>\n```\n\n這個組件有幾個問題．  \n由於這個組件是用 SFC 編寫的，沒有使用 `with` 語句．  \n換句話說，綁定沒有正常工作．\n\n讓我們看看編譯後的程式碼．\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: increment }), [\n      'count + count is: ',\n      _ctx.count + count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n- 問題 1：註冊為事件處理器的 `increment` 無法存取 `_ctx`．  \n  這是因為在之前的 `v-bind` 實現中沒有添加前綴．\n- 問題 2：表達式 `count + count` 無法存取 `_ctx`．  \n  關於 mustache 語法，它只在開頭添加 `_ctx.`，無法處理其他識別符．  \n  因此，表達式中出現的所有識別符都需要加上 `_ctx.` 前綴．這適用於所有部分，不僅僅是 mustache．\n\n看起來需要一個過程來為表達式中出現的識別符添加 `_ctx.`．\n\n::: details 期望的編譯結果\n\n```js\nconst _sfc_main = {\n  setup() {\n    const count = ref(0)\n    const increment = () => {\n      count.value++\n    }\n    return { count, increment }\n  },\n}\n\nfunction render(_ctx) {\n  const { h, mergeProps, normalizeProps, normalizeClass, normalizeStyle } =\n    ChibiVue\n\n  return h('div', null, [\n    '\\n    ',\n    h('button', normalizeProps({ onClick: _ctx.increment }), [\n      'count + count is: ',\n      _ctx.count + _ctx.count,\n    ]),\n    '\\n  ',\n  ])\n}\n\nexport default { ..._sfc_main, render }\n```\n\n:::\n\n::: warning\n\n實際上，原始實現採用了稍微不同的方法．\n\n如下所示，在原始實現中，從 `setup` 函式綁定的任何內容都透過 `$setup` 解析．\n\n![Original resolve bindings output](/figures/50-basic-template-compiler/transform-expression/resolve-bindings-original.png)\n\n然而，實現這個有點困難，所以我們將簡化它並透過添加 `_ctx.` 來實現．（所有 props 和 setup 都將從 `_ctx` 解析）\n\n:::\n\n## 實現方法\n\n簡單來說，我們想要做的是\"在 ExpressionNode 上的每個識別符（名稱）的開頭添加 `_ctx.`\"．\n\n讓我更詳細地解釋一下．  \n作為回顧，程式透過解析被表示為 AST．  \n表示程式的 AST 主要有兩種類型的節點：Expression 和 Statement．  \n這些通常被稱為表達式和語句．\n\n```ts\n1 // 這是一個 Expression\nident // 這是一個 Expression\nfunc() // 這是一個 Expression\nident + func() // 這是一個 Expression\n\nlet a // 這是一個 Statement\nif (!a) a = 1 // 這是一個 Statement\nfor (let i = 0; i < 10; i++) a++ // 這是一個 Statement\n```\n\n我們這裡要考慮的是 Expression．  \n有各種類型的表達式．Identifier 是其中之一，它是由識別符表示的表達式．  \n（你可以將其視為一般的變數名）\n\nIdentifier 出現在表達式的各個地方．\n\n```ts\n1 // 無\nident // ident --- (1)\nfunc() // func --- (2)\nident + func() // ident, func --- (3)\n```\n\n這樣，Identifier 出現在表達式的各個地方．\n\n你可以透過在以下網站輸入程式來觀察 ExpressionNode 上的各種 Identifier，該網站允許你觀察 AST．  \nhttps://astexplorer.net/#/gist/670a1bee71dbd50bec4e6cc176614ef8/9a9ff250b18ccd9000ed253b0b6970696607b774\n\n## 搜尋識別符\n\n現在我們知道了我們想要做什麼，我們如何實現它？\n\n看起來很困難，但實際上很簡單．我們將使用一個名為 estree-walker 的函式庫．  \nhttps://github.com/Rich-Harris/estree-walker\n\n我們將使用這個函式庫來遍歷透過 babel 解析獲得的 AST．  \n用法非常簡單．只需將 AST 傳遞給 `walk` 函式，並將每個 Node 的處理描述為第二個參數．  \n這個 `walk` 函式逐個節點遍歷 AST，到達該 Node 時的處理透過 `enter` 選項完成．  \n除了 `enter`，還有像 `leave` 這樣的選項來在該 Node 結束時處理．我們這次只使用 `enter`．\n\n創建一個名為 `compiler-core/babelUtils.ts` 的新檔案，並實現可以對 Identifier 執行操作的實用函式．\n\n首先，安裝 estree-walker．\n\n```sh\nnpm install estree-walker\n\nnpm install -D @babel/types # 也安裝這個\n```\n\n```ts\nimport { Identifier, Node } from '@babel/types'\n\nimport { walk } from 'estree-walker'\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        onIdentifier(node)\n      }\n    },\n  })\n}\n```\n\n然後，為表達式生成 AST 並將其傳遞給此函式，在重寫節點的同時執行轉換．\n\n## transformExpression 的實現\n\n### InterpolationNode 的 AST 和解析器更改\n\n我們將實現轉換過程的主體 transformExpression．\n\n首先，我們將修改 InterpolationNode，使其具有 SimpleExpressionNode 而不是字串作為其內容．\n\n```ts\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION\n  content: string // [!code --]\n  content: ExpressionNode // [!code ++]\n}\n```\n\n透過這個更改，我們還需要修改 parseInterpolation．\n\n```ts\nfunction parseInterpolation(\n  context: ParserContext,\n): InterpolationNode | undefined {\n  // .\n  // .\n  // .\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  }\n}\n```\n\n### 轉換器的實現（主體）\n\n為了使表達式轉換在其他轉換器中可用，我們將其提取為名為 `processExpression` 的函式．\n在 transformExpression 中，我們將處理 INTERPOLATION 和 DIRECTIVE 的 ExpressionNode．\n\n```ts\nexport const transformExpression: NodeTransform = node => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (dir.type === NodeTypes.DIRECTIVE) {\n        const exp = dir.exp\n        const arg = dir.arg\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n          dir.exp = processExpression(exp)\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg)\n        }\n      }\n    }\n  }\n}\n\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // TODO:\n}\n```\n\n接下來，讓我們解釋 processExpression 的實現．\n首先，我們將實現一個名為 rewriteIdentifier 的函式來重寫 node 內的 Identifier．\n如果 node 是單個 Identifier，我們簡單地應用此函式並返回它．\n\n需要注意的一點是，這個 processExpression 特定於 SFC（單檔案組件）情況（不使用 with 語句的情況）．\n換句話說，如果設置了 isBrowser 標誌，我們實現它簡單地返回 node．\n我們修改實現以透過 ctx 接收標誌．\n\n另外，我想保留像 true 和 false 這樣的字面量，所以我將為字面量創建一個白名單．\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    // 對瀏覽器不做任何處理\n    return node\n  }\n\n  const rawExp = node.content\n\n  const rewriteIdentifier = (raw: string) => {\n    return `_ctx.${raw}`\n  }\n\n  if (isSimpleIdentifier(rawExp)) {\n    node.content = rewriteIdentifier(rawExp)\n    return node\n  }\n\n  // TODO:\n}\n```\n\n`makeMap` 是在 vuejs/core 中實現的用於存在性檢查的輔助函式，它返回一個布林值，指示是否與用逗號分隔定義的字串匹配．\n\n```ts\nexport function makeMap(\n  str: string,\n  expectsLowerCase?: boolean,\n): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null)\n  const list: Array<string> = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]\n}\n```\n\n問題在於下一步，即如何轉換 SimpleExpressionNode（不是簡單的 Identifier）並轉換節點．\n在以下討論中，請注意我們將處理兩個不同的 AST：Babel 生成的 JavaScript AST 和 chibivue 定義的 AST．\n為了避免混淆，我們在本章中將前者稱為 estree，後者稱為 AST．\n\n策略分為兩個階段．\n\n1. 在收集節點的同時替換 estree 節點\n2. 基於收集的節點構建 AST\n\n首先，讓我們從階段 1 開始．\n這相對簡單．如果我們可以用 Babel 解析原始 SimpleExpressionNode 內容（字串）並獲得 estree，我們可以透過我們之前創建的實用函式傳遞它並應用 rewriteIdentifier．\n此時，我們收集 estree 節點．\n\n```ts\nimport { parse } from '@babel/parser'\nimport { Identifier } from '@babel/types'\nimport { walkIdentifiers } from '../babelUtils'\n\ninterface PrefixMeta {\n  start: number\n  end: number\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n): ExpressionNode {\n  // .\n  // .\n  // .\n  const ast = parse(`(${rawExp})`).program // ※ 這個 ast 指的是 estree。\n  type QualifiedId = Identifier & PrefixMeta\n  const ids: QualifiedId[] = []\n\n  walkIdentifiers(ast, node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  })\n\n  // TODO:\n}\n```\n\n需要注意的一點是，到目前為止，我們只操作了 estree，沒有操作 ast 節點．\n\n### CompoundExpression\n\n接下來，讓我們進入階段 2．在這裡，我們將定義一個名為 `CompoundExpressionNode` 的新 AST Node．\nCompound 意味著\"組合\"或\"複雜性\"．這個 Node 有 children，它們採用稍微特殊的值．\n首先，讓我們看看 AST 的定義．\n\n```ts\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n}\n```\n\nChildren 採用如上所示的陣列．\n要理解這個 Node 中的 children 代表什麼，看具體例子會更容易，所以讓我們給出一些例子．\n\n以下表達式將被解析為以下 CompoundExpressionNode：\n\n```ts\ncount * 2\n```\n\n```json\n{\n  \"type\": 7,\n  \"children\": [\n    {\n      \"type\": 4,\n      \"isStatic\": false,\n      \"content\": \"_ctx.count\"\n    },\n    \" * 2\"\n  ]\n}\n```\n\n這是一種相當奇怪的感覺．\"children\" 採用字串類型的原因是因為它採用這種形式．\n在 CompoundExpression 中，Vue 編譯器將其分為必要的粒度，並部分表示為字串或部分表示為 Node．\n具體來說，在像這樣重寫 Expression 中存在的 Identifier 的情況下，只有 Identifier 部分被分為另一個 SimpleExpressionNode．\n\n換句話說，我們要做的是基於收集的 estree 的 Identifier Node 和源生成這個 CompoundExpression．\n以下程式碼是為此的實現．\n\n```ts\nexport function processExpression(node: SimpleExpressionNode): ExpressionNode {\n  // .\n  // .\n  // .\n  const children: CompoundExpressionNode['children'] = []\n  ids.sort((a, b) => a.start - b.start)\n  ids.forEach((id, i) => {\n    const start = id.start - 1\n    const end = id.end - 1\n    const last = ids[i - 1]\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start)\n    if (leadingText.length) {\n      children.push(leadingText)\n    }\n\n    const source = rawExp.slice(start, end)\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    )\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end))\n    }\n  })\n\n  let ret\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc)\n  } else {\n    ret = node\n  }\n\n  return ret\n}\n```\n\nBabel 解析的 Node 有 start 和 end（它對應於原始字串的位置資訊），所以我們基於此從 rawExp 中提取相應的部分並仔細分割．\n請仔細查看原始碼了解更多詳細資訊．如果你理解到目前為止的策略，你應該能夠閱讀它．（另外，請查看 advancePositionWithClone 等的實現，因為它們是新實現的．）\n\n現在我們可以生成 CompoundExpressionNode，讓我們也在 Codegen 中支援它．\n\n```ts\nfunction genInterpolation(\n  node: InterpolationNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  genNode(node.content, context, option)\n}\n\nfunction genCompoundExpression(\n  node: CompoundExpressionNode,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i]\n    if (isString(child)) {\n      // 如果是字串，按原樣推送\n      context.push(child)\n    } else {\n      // 對於其他任何內容，為 Node 生成 codegen\n      genNode(child, context, option)\n    }\n  }\n}\n```\n\n（genInterpolation 已經變成了只是 genNode，但我現在將保留它．）\n\n## 試試看\n\n現在我們已經實現到這裡，讓我們完成編譯器並嘗試執行它！\n\n```ts\n// 添加 transformExpression\nexport function getBaseTransformPreset(): TransformPreset {\n  return [[transformElement], { bind: transformBind }] // [!code --]\n  return [[transformExpression, transformElement], { bind: transformBind }] // [!code ++]\n}\n```\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(3)\n    const getMsg = (count: number) => `Count: ${count}`\n    return { count, getMsg }\n  },\n\n  template: `\n    <div class=\"container\">\n      <p> {{ 'Message is \"' + getMsg(count) + '\"'}} </p>\n    </div>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/022_transform_expression)\n```\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/025-v-on.md",
    "content": "# v-on 的支援\n\n## 重構\n\n在繼續實現之前，讓我們進行一些重構．  \n目前，在 codegen 生成的程式碼中，我們從 `shared` 和 `runtime-core` 匯入（或解構）了許多輔助函式．  \n而且在 codegen（和 transform）的實現中，我們硬編碼了函式名稱．這不是很明智．\n\n這次，讓我們將它們重構為 `runtime-helper` 並用符號集中管理，進一步地，更改實現以僅匯入必要的內容．\n\n首先，讓我們在 `compiler-core/runtimeHelpers.ts` 中實現表示每個輔助函式的符號．  \n到目前為止，我們一直使用 `h` 函式來生成 VNode，但這次，讓我們按照原始實現更改為使用 `createVNode`．  \n從 `runtime-core/vnode` 匯出 `createVNode`，並在 `genVNodeCall` 中，更改程式碼以呼叫 `createVNode` 而不是 `genVNodeCall`．\n\n```ts\nexport const CREATE_VNODE = Symbol()\nexport const MERGE_PROPS = Symbol()\nexport const NORMALIZE_CLASS = Symbol()\nexport const NORMALIZE_STYLE = Symbol()\nexport const NORMALIZE_PROPS = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  [CREATE_VNODE]: 'createVNode',\n  [MERGE_PROPS]: 'mergeProps',\n  [NORMALIZE_CLASS]: 'normalizeClass',\n  [NORMALIZE_STYLE]: 'normalizeStyle',\n  [NORMALIZE_PROPS]: 'normalizeProps',\n}\n```\n\n使符號在 `CallExpression` 中可用作 `callee`．\n\n```ts\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION\n  callee: string | symbol\n}\n```\n\n在 `TransformContext` 中實現一個區域來註冊輔助函式和註冊它們的函式．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  currentNode: RootNode | TemplateChildNode | null\n  parent: ParentNode | null\n  childIndex: number\n  helpers: Map<symbol, number> // 這個\n  helper<T extends symbol>(name: T): T // 這個\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n    helpers: new Map(),\n    helper(name) {\n      const count = context.helpers.get(name) || 0\n      context.helpers.set(name, count + 1)\n      return name\n    },\n  }\n\n  return context\n}\n```\n\n用這個輔助函式替換硬編碼的部分，並修改 Preamble 以使用註冊的輔助函式．\n\n```ts\n// 示例)\npropsExpression = createCallExpression('mergeProps', mergeArgs, elementLoc)\n// ↓\npropsExpression = createCallExpression(\n  context.helper(MERGE_PROPS),\n  mergeArgs,\n  elementLoc,\n)\n```\n\n將 `context` 傳遞給 `createVNodeCall` 並在其中註冊 `CREATE_VNODE`．\n\n```ts\nexport function createVNodeCall(\n  context: TransformContext | null, // 這個\n  tag: VNodeCall['tag'],\n  props?: VNodeCall['props'],\n  children?: VNodeCall['children'],\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  // 這裡 ------------------------\n  if (context) {\n    context.helper(CREATE_VNODE)\n  }\n  // ------------------------\n\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    loc,\n  }\n}\n```\n\n```ts\nfunction genVNodeCall(\n  node: VNodeCall,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n  const { tag, props, children } = node\n\n  push(helper(CREATE_VNODE) + `(`, node) // 呼叫 createVNode\n  genNodeList(genNullableArgs([tag, props, children]), context, option)\n  push(`)`)\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  root.helpers = new Set([...context.helpers.keys()]) // 將輔助函式添加到 root\n}\n```\n\n```ts\n// 根據原始實現添加 `_` 作為前綴來為其設置別名\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName } = context\n\n  // 基於在 ast 中註冊的輔助函式生成輔助函式宣告\n  const helpers = Array.from(ast.helpers)\n  push(\n    `const { ${helpers.map(aliasHelper).join(', ')} } = ${runtimeGlobalName}\\n`,\n  )\n  newline()\n}\n```\n\n```ts\n// 在 genCallExpression 中處理符號並將它們轉換為輔助函式呼叫。\n\nexport interface CodegenContext {\n  // .\n  // .\n  // .\n  helper(key: symbol): string\n}\n\nfunction createCodegenContext(ast: RootNode): CodegenContext {\n  const context: CodegenContext = {\n    // .\n    // .\n    // .\n    helper(key) {\n      return `_${helperNameMap[key]}`\n    },\n  }\n  // .\n  // .\n  // .\n  return context\n}\n\n// .\n// .\n// .\n\nfunction genCallExpression(\n  node: CallExpression,\n  context: CodegenContext,\n  option: Required<CompilerOptions>,\n) {\n  const { push, helper } = context\n\n  // 如果是符號，從輔助函式中獲取它\n  const callee = isString(node.callee) ? node.callee : helper(node.callee)\n\n  push(callee + `(`, node)\n  genNodeList(node.arguments, context, option)\n  push(`)`)\n}\n```\n\n透過這樣，我們這次進行的重構就完成了．我們能夠清理硬編碼的部分！\n\n::: details 編譯結果\n\n※ 注意\n\n- 輸入使用的是前一個遊樂場的輸入\n- 實際上在 `function` 前面有一個 `return`\n- 生成的程式碼用 prettier 格式化\n\n當你這樣看時，有太多不必要的換行和空格...\n\n好吧，讓我們在其他地方改進這個．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      normalizeClass: _normalizeClass,\n    } = ChibiVue\n\n    return _createVNode('div', null, [\n      '\\n  ',\n      _createVNode('p', _normalizeProps({ id: count }), ' v-bind:id=\"count\" '),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ id: count * 2 }),\n        ' :id=\"count * 2\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' v-bind:[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ ['style' || '']: bind.style }),\n        ' :[\"style\"]=\"bind.style\" ',\n      ),\n      '\\n\\n  ',\n      _createVNode('p', _normalizeProps(bind), ' v-bind=\"bind\" '),\n      '\\n\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: { 'font-weight': 'bold' } }),\n        ' :style=\"{ font-weight: \\'bold\\' }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ style: 'font-weight: bold;' }),\n        ' :style=\"\\'font-weight: bold;\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass('my-class my-class2'),\n        }),\n        ' :class=\"\\'my-class my-class2\\'\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({ class: _normalizeClass(['my-class']) }),\n        ' :class=\"[\\'my-class\\']\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': true }),\n        }),\n        ' :class=\"{ \\'my-class\\': true }\" ',\n      ),\n      '\\n  ',\n      _createVNode(\n        'p',\n        _normalizeProps({\n          class: _normalizeClass({ 'my-class': false }),\n        }),\n        ' :class=\"{ \\'my-class\\': false }\" ',\n      ),\n      '\\n',\n    ])\n  }\n}\n```\n\n:::\n\n## v-on\n\n## 這次要實現的開發者介面\n\n現在讓我們繼續實現 v-on．\n\nv-on 也有各種開發者介面．\nhttps://vuejs.org/guide/essentials/event-handling.html\n\n這是我們這次的目標．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const count = ref(0)\n    const increment = (e: Event) => {\n      console.log(e)\n      count.value++\n    }\n    return { count, increment, state: { increment }, eventName: 'click' }\n  },\n\n  template: `<div>\n    <p>count: {{ count }}</p>\n\n    <button v-on:click=\"increment\">v-on:click=\"increment\"</button>\n    <button v-on:[eventName]=\"increment\">v-on:click=\"increment\"</button>\n    <button @click=\"increment\">@click=\"increment\"</button>\n    <button v-on=\"{ click: increment }\">v-on=\"{ click: increment }\"</button>\n\n    <button @click=\"state.increment\">v-on:click=\"increment\"</button>\n    <button @click=\"count++\">@click=\"count++\"</button>\n    <button @click=\"() => count++\">@click=\"() => count++\"</button>\n    <button @click=\"increment($event)\">@click=\"increment($event)\"</button>\n    <button @click=\"e => increment(e)\">@click=\"e => increment(e)\"</button>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n## 我想要做的事情\n\n實際上，關於解析器的實現，前一章的實現就足夠了，問題在於轉換器的實現．\n轉換的內容主要根據 arg 的存在與否和 exp 的類型而變化．\n當沒有 arg 時，需要做的事情幾乎與 v-bind 相同．\n\n換句話說，需要考慮的是可以作為 arg 的 exp 類型以及為它們轉換必要的 AST Node．\n\n- 任務 1\n  分配一個函式．\n  這是最簡單的情況．\n\n  ```html\n  <button v-on:click=\"increment\">increment</button>\n  ```\n\n- 任務 2\n  在現場編寫函式表達式．\n  在這種情況下，你可以接收事件作為第一個參數．\n\n  ```html\n  <button v-on:click=\"(e) => increment(e)\">increment</button>\n  ```\n\n- 任務 3\n  編寫除函式以外的語句．\n\n  ```html\n  <button @click=\"count = 0\">reset</button>\n  ```\n\n  看起來這個表達式需要轉換為以下函式．\n\n  ```ts\n  ;() => {\n    count = 0\n  }\n  ```\n\n- 任務 4\n  在像任務 3 這樣的情況下，你可以使用識別符 `$event`．\n  這是處理事件物件的情況．\n\n  ```ts\n  const App = defineComponent({\n    setup() {\n      const count = ref(0)\n      const increment = (e: Event) => {\n        console.log(e)\n        count.value++\n      }\n      return { count, increment, object }\n    },\n\n    template: `\n      <div class=\"container\">\n        <button @click=\"increment($event)\">increment($event)</button>\n        <p> {{ count }} </p>\n      </div>\n      `,\n  })\n  // 不能像 @click=\"() => increment($event)\" 這樣使用。\n  ```\n\n  看起來它需要轉換為以下函式．\n\n  ```ts\n  $event => {\n    increment($event)\n  }\n  ```\n\n## 實現\n\n### 當沒有 arg 時\n\n暫時，讓我們實現沒有 arg 的情況，因為它與 v-bind 相同．\n這是我在前一章中留下 TODO 註釋的部分．它在 transformElement 附近．\n\n```ts\nconst isVBind = name === 'bind'\nconst isVOn = name === 'on' // --------------- 這裡\n\n// v-bind 和 v-on 沒有參數的特殊情況\nif (!arg && (isVBind || isVOn)) {\n  if (exp) {\n    if (isVBind) {\n      pushMergeArg()\n      mergeArgs.push(exp)\n    } else {\n      // -------------------------------------- 這裡\n      // v-on=\"obj\" -> toHandlers(obj)\n      pushMergeArg({\n        type: NodeTypes.JS_CALL_EXPRESSION,\n        loc,\n        callee: context.helper(TO_HANDLERS),\n        arguments: [exp],\n      })\n    }\n  }\n  continue\n}\n\nconst directiveTransform = context.directiveTransforms[name]\nif (directiveTransform) {\n  const { props } = directiveTransform(prop, node, context)\n  if (isVOn && arg && !isStaticExp(arg)) {\n    pushMergeArg(createObjectExpression(props, elementLoc))\n  } else {\n    properties.push(...props)\n  }\n} else {\n  // TODO: 自訂指令。\n}\n```\n\n我將這次實現名為 `TO_HANDLERS` 的輔助函式．\n\n這個函式將以 `v-on=\"{ click: increment }\"` 形式傳遞的物件轉換為 `{ onClick: increment }` 的形式．\n沒有什麼特別困難的．\n\n```ts\nimport { toHandlerKey } from '../../shared'\n\n/**\n * 用於在 v-on=\"obj\" 中為鍵添加 \"on\" 前綴\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {}\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key]\n  }\n  return ret\n}\n```\n\n這完成了沒有 arg 時的實現．\n讓我們繼續實現有 arg 時的情況．\n\n### transformVOn\n\n現在，讓我們繼續這次的主題，即 v-on．v-on 的 exp 有各種格式．\n\n```ts\nincrement\n\nstate.increment\n\ncount++\n\n;() => count++\n\nincrement($event)\n\ne => increment(e)\n```\n\n首先，這些格式可以大致分為兩類：\"函式\"和\"語句\"．在 Vue 中，如果是單個 Identifier，單個 MemberExpression 或函式表達式，則將其視為函式．否則，它是一個語句．在原始碼中，它似乎被稱為 inlineStatement．\n\n```ts\n// 函式（※ 為了方便，請將這些視為函式表達式。）\nincrement\nstate.increment\n;() => count++\ne => increment(e)\n\n// inlineStatement\ncount++\nincrement($event)\n```\n\n換句話說，這次的實現流程如下：\n\n1. 首先，確定它是否是函式（單個 Identifier 或單個 MemberExpression 或函式表達式）．\n\n2-1. 如果是函式，生成 `eventName: exp` 形式的 ObjectProperty，不進行任何轉換．\n\n2-2. 如果不是函式（如果是 inlineStatement），將其轉換為 `$event => { ${exp} }` 的形式並生成 ObjectProperty．\n\n這就是基本思路．\n\n#### 確定是函式表達式還是語句\n\n讓我們從實現確定開始．是否是函式表達式是使用正規表達式完成的．\n\n```ts\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/\n\nconst isFn = fnExpRE.test(exp.content)\n```\n\n是否是單個 Identifier 或單個 MemberExpression 是用名為 `isMemberExpression` 的函式實現的．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\n```\n\n這個 `isMemberExpression` 函式相當複雜，實現很長．有點長，所以我在這裡省略它．（如果你感興趣，請查看程式碼．）\n\n一旦我們確定了這一點，它是 inlineStatement 的條件就是除了這些之外的任何東西．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isFnExp = fnExpRE.test(exp.content)\nconst isInlineStatement = !(isMemberExp || isFnExp)\n```\n\n現在我們已經確定了這一點，讓我們基於這個結果實現轉換過程．\n\n```ts\nconst isMemberExp = isMemberExpression(exp.content)\nconst isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))\nconst hasMultipleStatements = exp.content.includes(`;`)\n\nif (isInlineStatement) {\n  // 將內聯語句包裝在函式表達式中\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\n### 問題\n\n實際上，上述實現有一個小問題．\n\n問題在於 `$event`，因為在 `dir.exp` 中，我們需要使用 `processExpression` 處理從 setup 綁定的值，但問題在於 `$event`．\n在 AST 上，`$event` 也被視為 Identifier，所以如果我們保持原樣，它將被加上 `_ctx.` 前綴．\n\n所以讓我們做一點改進．讓我們在 `transformContext` 中註冊一個局部變數．在 `walkIdentifiers` 中，如果有局部變數，我們不會執行 `onIdentifier`．\n\n```ts\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  identifiers: Object.create(null),\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      addId(exp)\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      removeId(exp)\n    }\n  },\n}\n\nfunction addId(id: string) {\n  const { identifiers } = context\n  if (identifiers[id] === undefined) {\n    identifiers[id] = 0\n  }\n  identifiers[id]!++\n}\n\nfunction removeId(id: string) {\n  context.identifiers[id]!--\n}\n```\n\n```ts\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null), // [!code ++]\n) {\n  ;(walk as any)(root, {\n    enter(node: Node) {\n      if (node.type === 'Identifier') {\n        const isLocal = !!knownIds[node.name] // [!code ++]\n        // prettier-ignore\n        if (!isLocal) { // [!code ++]\n          onIdentifier(node);\n        } // [!code ++]\n      }\n    },\n  })\n}\n```\n\n然後，當在 `processExpression` 中使用 `walkIdentifiers` 時，我們將從 `context` 中拉取 `identifiers`．\n\n```ts\nconst ids: QualifiedId[] = []\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers) // [!code ++]\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // [!code ++]\n)\n```\n\n最後，當在 `transformOn` 中轉換時，讓我們註冊 `$event`．\n\n```ts\n// prettier-ignore\nif (!context.isBrowser) { // [!code ++]\n  isInlineStatement && context.addIdentifiers(`$event`); // [!code ++]\n  exp = dir.exp = processExpression(exp, context); // [!code ++]\n  isInlineStatement && context.removeIdentifiers(`$event`); // [!code ++]\n} // [!code ++]\n\nif (isInlineStatement) {\n  // 將內聯語句包裝在函式表達式中\n  exp = createCompoundExpression([\n    `$event => ${hasMultipleStatements ? `{` : `(`}`,\n    exp,\n    hasMultipleStatements ? `}` : `)`,\n  ])\n}\n```\n\n由於 v-on 需要一些特殊處理，並且由於它在 `transformOn` 中單獨處理，我們將在 `transformExpression` 中跳過它．\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  // .\n  // .\n  // .\n  if (\n    exp &&\n    exp.type === NodeTypes.SIMPLE_EXPRESSION &&\n    !(dir.name === 'on' && arg) // [!code ++]\n  ) {\n    dir.exp = processExpression(exp, ctx)\n  }\n}\n```\n\n現在，我們已經完成了這次的關鍵部分．讓我們實現剩餘的必要部分並完成 v-on！！\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/025_v_on)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/027-event-modifier.md",
    "content": "# 事件修飾符\n\n## 這次要做的事情\n\n由於我們上次實現了 v-on 指令，現在讓我們實現事件修飾符．\n\nVue.js 有對應於 preventDefault 和 stopPropagation 的修飾符．\n\nhttps://vuejs.org/guide/essentials/event-handling.html\n\n這次，讓我們以以下開發者介面為目標．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const inputText = ref('')\n\n    const buffer = ref('')\n    const handleInput = (e: Event) => {\n      const target = e.target as HTMLInputElement\n      buffer.value = target.value\n    }\n    const submit = () => {\n      inputText.value = buffer.value\n      buffer.value = ''\n    }\n\n    return { inputText, buffer, handleInput, submit }\n  },\n\n  template: `<div>\n    <form @submit.prevent=\"submit\">\n      <label>\n        Input Data\n        <input :value=\"buffer\" @input=\"handleInput\" />\n      </label>\n      <button>submit</button>\n    </form>\n    <p>inputText: {{ inputText }}</p>\n</div>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n特別是，請注意以下部分．\n\n```html\n<form @submit.prevent=\"submit\"></form>\n```\n\n有一個 `@submit.prevent` 的描述．這意味著在呼叫 submit 事件處理器時，執行 `preventDefault`．\n\n如果不包含 `.prevent`，提交時頁面將重新載入．\n\n## AST 和解析器的實現\n\n由於我們要向模板添加新語法，需要對解析器和 AST 進行更改．\n\n首先，讓我們看看 AST．這很簡單，只需向 `DirectiveNode` 添加一個名為 `modifiers`（字串陣列）的屬性．\n\n```ts\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE\n  name: string\n  exp: ExpressionNode | undefined\n  arg: ExpressionNode | undefined\n  modifiers: string[] // 添加這個\n}\n```\n\n讓我們相應地實現解析器．\n\n實際上，這很容易，因為它已經包含在從原始原始碼借用的正規表達式中．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // .\n  // .\n  // .\n  const modifiers = match[3] ? match[3].slice(1).split('.') : [] // 從匹配結果中提取修飾符\n  return {\n    type: NodeTypes.DIRECTIVE,\n    name: dirName,\n    exp: value && {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      content: value.content,\n      isStatic: false,\n      loc: value.loc,\n    },\n    loc,\n    arg,\n    modifiers, // 包含在返回中\n  }\n}\n```\n\n是的．透過這樣，AST 和解析器的實現就完成了．\n\n## compiler-dom/transform\n\n讓我們稍微回顧一下當前的編譯器架構．\n\n當前的配置如下．\n\n![Compiler architecture before DOM modifiers](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-core-only.svg)\n\n當你再次理解 compiler-core 和 compiler-dom 的角色時，  \ncompiler-core 提供不依賴於 DOM 的編譯器功能，如生成和轉換 AST．\n\n到目前為止，我們在 compiler-core 中實現了 v-on 指令，但這只是將符號 `@click=\"handle\"` 轉換為物件 `{ onClick: handle }`，  \n它不執行任何依賴於 DOM 的處理．\n\n現在，讓我們看看這次我們想要實現的內容．  \n這次，我們想要生成實際執行 `e.preventDefault()` 或 `e.stopPropagation()` 的程式碼．  \n這些嚴重依賴於 DOM．\n\n因此，我們也將在 compiler-dom 端實現轉換器．我們將在這裡實現與 DOM 相關的轉換器．\n\n在 compiler-core 中，我們需要考慮 compiler-core 中的 transform 和在 compiler-dom 中實現的 transform 之間的交互．  \n交互是如何在執行 compiler-core 中的 transform 的同時實現在 compiler-dom 中實現的 transform．\n\n所以首先，讓我們修改在 compiler-core 中實現的 `DirectiveTransform` 介面．\n\n```ts\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult, // 添加\n) => DirectiveTransformResult\n```\n\n我添加了 `augmentor`．  \n嗯，這只是一個回呼函式．透過允許接收回呼作為 `DirectiveTransform` 介面的一部分，我們使轉換函式可擴展．\n\n在 compiler-dom 中，我們將實現一個包裝在 compiler-core 中實現的轉換器的轉換器．\n\n```ts\n// 實現示例\n\n// compiler-dom 端的實現\n\nimport { transformOn as baseTransformOn } from 'compiler-core'\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransformOn(dir, node, context, () => {\n    /** 在這裡實現 compiler-dom 自己的實現 */\n    return {\n      /** */\n    }\n  })\n}\n```\n\n如果你將在 compiler-dom 端實現的這個 `transformOn` 作為選項傳遞給編譯器，就可以了．  \n這是關係的圖表．  \n不是從 compiler-dom 傳遞所有轉換器，而是在 compiler-core 中實現預設實現，配置允許添加額外的轉換器．\n\n![Compiler architecture with DOM augmentor](/figures/50-basic-template-compiler/event-modifier/compiler-architecture-dom-augmentor.svg)\n\n透過這樣，compiler-core 可以執行不依賴於 DOM 的轉換器，compiler-dom 可以在執行 compiler-core 中的轉換器的同時實現依賴於 DOM 的處理．\n\n## 轉換器的實現\n\n現在，讓我們在 compiler-dom 端實現轉換器．\n\n我們應該如何轉換它？現在，由於即使我們簡單地說\"修飾符\"也有各種類型的修飾符，讓我們對它們進行分類，以便我們可以考慮未來的可能性．\n\n這次，我們將實現\"事件修飾符\"．讓我們首先將其提取為 `eventModifiers`．\n\n```ts\nconst isEventModifier = makeMap(\n  // 事件傳播管理\n  `stop,prevent,self`,\n)\n\nconst resolveModifiers = (modifiers: string[]) => {\n  const eventModifiers = []\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i]\n    if (isEventModifier(modifier)) {\n      eventModifiers.push(modifier)\n    }\n  }\n\n  return { eventModifiers }\n}\n```\n\n現在我們已經提取了 `eventModifiers`，我們應該如何使用它？總之，我們將在 runtime-dom 端實現一個名為 `withModifiers` 的輔助函式，並將其轉換為呼叫該函式的表達式．\n\n```ts\n// runtime-dom/runtimeHelpers.ts\n\nexport const V_ON_WITH_MODIFIERS = Symbol()\n```\n\n```ts\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, baseResult => {\n    const { modifiers } = dir\n    if (!modifiers.length) return baseResult\n\n    let { key, value: handlerExp } = baseResult.props[0]\n    const { eventModifiers } = resolveModifiers(modifiers)\n\n    if (eventModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(eventModifiers),\n      ])\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    }\n  })\n}\n```\n\n透過這樣，轉換器的實現幾乎完成了．\n\n現在讓我們在 compiler-dom 端實現 `withModifiers`．\n\n## `withModifiers` 的實現\n\n讓我們在 runtime-dom/directives/vOn.ts 中繼續實現．\n\n實現非常簡單．\n\n為事件修飾符實現一個保護函式，並實現它，使其執行與陣列中接收的修飾符數量一樣多的次數．\n\n```ts\nconst modifierGuards: Record<string, (e: Event) => void | boolean> = {\n  stop: e => e.stopPropagation(),\n  prevent: e => e.preventDefault(),\n  self: e => e.target !== e.currentTarget,\n}\n\nexport const withModifiers = (fn: Function, modifiers: string[]) => {\n  return (event: Event, ...args: unknown[]) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]]\n      if (guard && guard(event)) return\n    }\n    return fn(event, ...args)\n  }\n}\n```\n\n這就是實現的結束．\n\n讓我們檢查操作！如果按下按鈕時輸入內容反映在螢幕上而頁面沒有重新載入，就可以了！\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier)\n\n## 其他修飾符\n\n現在我們已經走到這一步，讓我們實現其他修飾符．\n\n基本的實現方法是相同的．\n\n讓我們按如下方式對修飾符進行分類：\n\n```ts\nconst keyModifiers = []\nconst nonKeyModifiers = []\nconst eventOptionModifiers = []\n```\n\n然後，生成必要的映射並用 `resolveModifiers` 對它們進行分類．\n\n需要注意的兩點是：\n\n- 修飾符名稱和實際 DOM API 名稱之間的差異\n- 實現一個新的輔助函式來執行特定的鍵事件（withKeys）\n\n請在閱讀實際程式碼的同時嘗試實現！\n如果你已經走到這一步，你應該能夠做到．\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/027_event_modifier2)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/030-fragment.md",
    "content": "# 實現 Fragment\n\n## 當前實現的問題\n\n讓我們嘗試在遊樂場中執行以下程式碼：\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n你可能會遇到這樣的錯誤：\n\n![Fragment error result in the browser](/figures/50-basic-template-compiler/fragment/fragment-error-result.png)\n\n查看錯誤訊息，似乎與 Function 建構函式有關．\n\n換句話說，程式碼生成似乎在某種程度上是成功的，所以讓我們看看實際生成了什麼程式碼．\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode } = ChibiVue\n\n    return _createVNode(\"header\", null, \"header\")\"\\n  \"_createVNode(\"main\", null, \"main\")\"\\n  \"_createVNode(\"footer\", null, \"footer\")\n   }\n}\n```\n\n`return` 語句後的程式碼是不正確的．當前的程式碼生成實現不處理根是陣列（即不是單個節點）的情況．\n\n我們將修復這個問題．\n\n## 應該生成什麼程式碼？\n\n即使我們正在進行修改，應該生成什麼樣的程式碼？\n\n總之，程式碼應該看起來像這樣：\n\n```ts\nreturn function render(_ctx) {\n  with (_ctx) {\n    const { createVNode: _createVNode, Fragment: _Fragment } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      [\n        _createVNode('header', null, 'header'),\n        '\\n  ',\n        _createVNode('main', null, 'main'),\n        '\\n  ',\n        _createVNode('footer', null, 'footer'),\n      ],\n    ])\n  }\n}\n```\n\n這個 `Fragment` 是在 Vue 中定義的符號．\n\n換句話說，Fragment 不像 FragmentNode 那樣表示為 AST，而是簡單地作為 ElementNode 的標籤．\n\n我們將在渲染器中實現 Fragment 的處理，類似於 Text．\n\n## 實現\n\nFragment 符號將在 runtime-core/vnode.ts 中實現．\n\n讓我們將其作為 VNodeTypes 中的新類型添加．\n\n```ts\nexport type VNodeTypes = Component | typeof Text | typeof Fragment | string\n\nexport const Fragment = Symbol()\n```\n\n實現渲染器．\n\n在 patch 函式中為 fragment 添加分支．\n\n```ts\nif (type === Text) {\n  processText(n1, n2, container, anchor)\n} else if (shapeFlag & ShapeFlags.ELEMENT) {\n  processElement(n1, n2, container, anchor, parentComponent)\n} else if (type === Fragment) {\n  // 這裡\n  processFragment(n1, n2, container, anchor, parentComponent)\n} else if (shapeFlag & ShapeFlags.COMPONENT) {\n  processComponent(n1, n2, container, anchor, parentComponent)\n} else {\n  // do nothing\n}\n```\n\n注意插入或刪除元素通常應該用 anchor 作為標記來實現．\n\n顧名思義，anchor 表示 fragment 的開始和結束位置．\n\n起始元素由 VNode 中現有的 `el` 屬性表示，但目前沒有表示結束的屬性．讓我們添加它．\n\n```ts\nexport interface VNode<HostNode = any> {\n  // .\n  // .\n  // .\n  anchor: HostNode | null // fragment anchor // 添加\n  // .\n  // .\n}\n```\n\n在掛載期間設置 anchor．\n\n在 mount/patch 中將 fragment 的結束作為 anchor 傳遞．\n\n```ts\nconst processFragment = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!\n  const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!\n\n  if (n1 == null) {\n    hostInsert(fragmentStartAnchor, container, anchor)\n    hostInsert(fragmentEndAnchor, container, anchor)\n    mountChildren(\n      n2.children as VNode[],\n      container,\n      fragmentEndAnchor,\n      parentComponent,\n    )\n  } else {\n    patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent)\n  }\n}\n```\n\n當 fragment 的元素在更新期間發生變化時要小心．\n\n```ts\nconst move = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const { type, children, el, shapeFlag } = vnode\n\n  // .\n\n  if (type === Fragment) {\n    hostInsert(el!, container, anchor)\n    for (let i = 0; i < (children as VNode[]).length; i++) {\n      move((children as VNode[])[i], container, anchor)\n    }\n    hostInsert(vnode.anchor!, container, anchor) // 插入 anchor\n    return\n  }\n  // .\n  // .\n  // .\n}\n```\n\n在卸載期間，也依賴 anchor 來刪除元素．\n\n```ts\nconst remove = (vnode: VNode) => {\n  const { el, type, anchor } = vnode\n  if (type === Fragment) {\n    removeFragment(el!, anchor!)\n  }\n\n  // .\n  // .\n  // .\n}\n\nconst removeFragment = (cur: RendererNode, end: RendererNode) => {\n  let next\n  while (cur !== end) {\n    next = hostNextSibling(cur)! // ※ 將此添加到 nodeOps！\n    hostRemove(cur)\n    cur = next\n  }\n  hostRemove(end)\n}\n```\n\n## 測試\n\n我們之前編寫的程式碼應該正確工作．\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\nconst App = defineComponent({\n  template: `<header>header</header>\n<main>main</main>\n<footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n目前，我們不能使用像 v-for 這樣的指令，所以我們不能編寫在模板中使用 fragment 並改變元素數量的描述．\n\n讓我們透過編寫編譯後的程式碼來模擬行為，看看它是如何工作的．\n\n```ts\nimport { Fragment, createApp, defineComponent, h, ref } from 'chibivue'\n\n// const App = defineComponent({\n//   template: `<header>header</header>\n//   <main>main</main>\n//   <footer>footer</footer>`,\n// });\n\nconst App = defineComponent({\n  setup() {\n    const list = ref([0])\n    const update = () => {\n      list.value = [...list.value, list.value.length]\n    }\n    return () =>\n      h(Fragment, {}, [\n        h('button', { onClick: update }, 'update'),\n        ...list.value.map(i => h('div', {}, i)),\n      ])\n  },\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n看起來工作正常！\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/030_fragment)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/035-comment.md",
    "content": "# 實現註釋\n\n## 目標開發者介面\n\n```ts\nimport { createApp, defineComponent } from 'chibivue'\n\nconst App = defineComponent({\n  template: `\n  <!-- this is header. -->\n  <header>header</header>\n\n  <!-- \n    this is main.\n    main content is here!\n  -->\n  <main>main</main>\n\n  <!-- this is footer -->\n  <footer>footer</footer>`,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n無需進一步解釋．\n\n## AST 和解析器的實現\n\n關於如何實現註釋，乍一看，似乎我們可以在解析時簡單地忽略它．\n\n然而，在 Vue 中，模板中編寫的註釋會按原樣作為 HTML 輸出．\n\n換句話說，註釋也需要被渲染，所以需要在 VNode 上有一個表示，編譯器也需要輸出該程式碼．\n此外，還需要一個生成註釋節點的操作．\n\n首先，讓我們實現 AST 和解析器．\n\n### AST\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  COMMENT,\n  // .\n  // .\n  // .\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT\n  content: string\n}\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n```\n\n### 解析器\n\n現在，讓我們拋出一個錯誤．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  // .\n  // .\n  // .\n  if (startsWith(s, '{{')) {\n    node = parseInterpolation(context)\n  } else if (s[0] === '<') {\n    if (s[1] === '!') {\n      // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n      if (startsWith(s, '<!--')) {\n        node = parseComment(context)\n      }\n    } else if (/[a-z]/i.test(s[1])) {\n      node = parseElement(context, ancestors)\n    }\n  }\n  // .\n  // .\n  // .\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context)\n  let content: string\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source)\n  if (!match) {\n    content = context.source.slice(4)\n    advanceBy(context, context.source.length)\n    throw new Error('EOF_IN_COMMENT') // TODO: 錯誤處理\n  } else {\n    if (match.index <= 3) {\n      throw new Error('ABRUPT_CLOSING_OF_EMPTY_COMMENT') // TODO: 錯誤處理\n    }\n    if (match[1]) {\n      throw new Error('INCORRECTLY_CLOSED_COMMENT') // TODO: 錯誤處理\n    }\n    content = context.source.slice(4, match.index)\n\n    const s = context.source.slice(0, match.index)\n    let prevIndex = 1,\n      nestedIndex = 0\n    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1)\n      if (nestedIndex + 4 < s.length) {\n        throw new Error('NESTED_COMMENT') // TODO: 錯誤處理\n      }\n      prevIndex = nestedIndex + 1\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1)\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  }\n}\n```\n\n## 程式碼生成\n\n向 runtime-core 添加表示 Comment 的 VNode．\n\n```ts\nexport const Comment = Symbol()\nexport type VNodeTypes =\n  | string\n  | Component\n  | typeof Text\n  | typeof Comment\n  | typeof Fragment\n```\n\n實現一個名為 createCommentVNode 的函式並將其作為輔助函式公開．\n\n在 codegen 中，生成呼叫 createCommentVNode 的程式碼．\n\n```ts\nexport function createCommentVNode(text: string = ''): VNode {\n  return createVNode(Comment, null, text)\n}\n```\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    // .\n    // .\n    // .\n    case NodeTypes.COMMENT:\n      genComment(node, context)\n      break\n    // .\n    // .\n    // .\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)\n}\n```\n\n## 渲染\n\n讓我們實現渲染器．\n\n像往常一樣，在 patch 中分支 Comment 的情況，並在掛載時生成註釋．\n\n關於 patch，由於這次是靜態的，我不會做任何特殊的事情．（在程式碼中，它只是設置為按原樣分配．）\n\n```ts\nconst patch = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  const { type, ref, shapeFlag } = n2\n  if (type === Text) {\n    processText(n1, n2, container, anchor)\n  } else if (type === Comment) {\n    processCommentNode(n1, n2, container, anchor)\n  } //.\n  //.\n  //.\n}\n\nconst processCommentNode = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  if (n1 == null) {\n    hostInsert(\n      (n2.el = hostCreateComment((n2.children as string) || '')), // 在 nodeOps 端實現 hostCreateComment！\n      container,\n      anchor,\n    )\n  } else {\n    n2.el = n1.el\n  }\n}\n```\n\n好了，你現在應該已經實現了註釋．讓我們檢查實際操作！\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/035_comment)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/040-v-if-and-structural-directive.md",
    "content": "# v-if 和結構指令\n\n現在讓我們繼續實現指令！\n\n最後，我們將實現 v-if．\n\n## v-if 指令與之前指令的區別\n\n到目前為止，我們已經實現了 v-bind 和 v-on 等指令．\n\n現在讓我們實現 v-if，但 v-if 與這些指令略有不同．\n\n根據 Vue.js 官方文件關於編譯時優化的摘錄，\n\n> 在這種情況下，整個模板有一個單一的塊，因為它不包含任何結構指令，如 v-if 和 v-for。\n\nhttps://vuejs.org/guide/extras/rendering-mechanism.html#tree-flattening\n\n如你所見，可以找到\"結構指令\"這個詞．（你不必擔心什麼是 Tree Flattening，因為它將單獨解釋．）\n\n如前所述，v-if 和 v-for 被稱為\"結構指令\"，是涉及結構的指令．\n\n在 Angular 的文件中，它們也被明確提及．\n\nhttps://angular.jp/guide/structural-directives\n\nv-if 和 v-for 是不僅改變元素的屬性（以及事件的行為），還透過切換元素的存在或根據列表中項目的數量生成/刪除元素來改變元素結構的指令．\n\n## 期望的開發者介面\n\n讓我們考慮如何結合 v-if / v-else-if / v-else 來實現 FizzBuzz．\n\n```ts\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst App = defineComponent({\n  setup() {\n    const n = ref(1)\n    const inc = () => {\n      n.value++\n    }\n\n    return { n, inc }\n  },\n\n  template: `\n    <button @click=\"inc\">inc</button>\n    <p v-if=\"n % 5 === 0 && n % 3 === 0\">FizzBuzz</p>\n    <p v-else-if=\"n % 5 === 0\">Buzz</p>\n    <p v-else-if=\"n % 3 === 0\">Fizz</p>\n    <p v-else>{{ n }}</p>\n  `,\n})\n\nconst app = createApp(App)\n\napp.mount('#app')\n```\n\n首先，讓我們考慮我們想要生成的程式碼．\n\n簡單地說，v-if 和 v-else 被轉換為如下的條件表達式：\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\n如你所見，我們正在向到目前為止實現的程式碼添加結構．\n\n要實現將 AST 轉換為此類程式碼的轉換器，我們需要進行一些修改．\n\n::: warning\n\n當前實現不處理空白和其他跳過，因此中間可能有不必要的文字節點．\n\n但是，v-if 的實現沒有問題（你稍後會看到），所以現在請忽略它．\n\n:::\n\n## 結構指令的實現\n\n### 實現與結構相關的方法\n\n在實現 v-if 之前，讓我們做一些準備．\n\n如前所述，v-if 和 v-for 是修改 AST 節點結構的結構指令．\n\n為了實現這一點，我們需要在基礎轉換器中實現幾個方法．\n\n具體來說，我們將在 TransformContext 中實現以下三個方法：\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  replaceNode(node: TemplateChildNode): void // 添加\n  removeNode(node?: TemplateChildNode): void // 添加\n  onNodeRemoved(): void // 添加\n}\n```\n\n由於你已經在實現 traverseChildren，我認為你已經在跟蹤當前父級和子級的索引．你可以使用它們來實現上述方法．\n\n<!-- NOTE: You may not need to implement this chapter yet. -->\n\n::: details 以防萬一\n\n這部分：\n\n我認為你已經實現了它，但我會解釋一下，以防萬一，因為我在實現它的章節中沒有詳細解釋．\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  for (let i = 0; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent // 這個\n    context.childIndex = i // 這個\n    traverseNode(child, context)\n  }\n}\n```\n\n:::\n\n```ts\nexport function createTransformContext(\n  root: RootNode,\n  { nodeTransforms = [], directiveTransforms = {} }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    // .\n    // .\n\n    // 用給定節點替換當前節點和相應父級的子級\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node\n    },\n\n    // 從當前節點的父級的子級中刪除給定節點\n    removeNode(node) {\n      const list = context.parent!.children\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1\n      if (!node || node === context.currentNode) {\n        // 當前節點被刪除\n        context.currentNode = null\n        context.onNodeRemoved()\n      } else {\n        // 兄弟節點被刪除\n        if (context.childIndex > removalIndex) {\n          context.childIndex--\n          context.onNodeRemoved()\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1)\n    },\n\n    // 這在使用 replaceNode 等時註冊\n    onNodeRemoved: () => {},\n  }\n\n  return context\n}\n```\n\n現有實現也需要一些修改．調整 traverseChildren 以處理呼叫 removeNode 的情況．\n\n由於刪除節點時索引會發生變化，因此在刪除節點時減少索引．\n\n```ts\nexport function traverseChildren(\n  parent: ParentNode,\n  context: TransformContext,\n) {\n  let i = 0 // 這個\n  const nodeRemoved = () => {\n    i-- // 這個\n  }\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i]\n    if (isString(child)) continue\n    context.parent = parent\n    context.childIndex = i\n    context.onNodeRemoved = nodeRemoved // 這個\n    traverseNode(child, context)\n  }\n}\n```\n\n### createStructuralDirectiveTransform 的實現\n\n為了實現 v-if 和 v-for 等指令，我們將實現一個名為 createStructuralDirectiveTransform 的輔助函式．\n\n這些轉換器只作用於 NodeTypes.ELEMENT，並將每個轉換器的實現應用於 Node 擁有的 DirectiveNode．\n\n嗯，實現本身並不大，所以我認為如果你實際看到它會更容易理解．它看起來像這樣：\n\n```ts\n// 每個轉換器（v-if/v-for 等）都根據此介面實現。\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void)\n\nexport function createStructuralDirectiveTransform(\n  // 名稱也支援正規表達式。\n  // 例如，在 v-if 的轉換器中，假設接收類似 /^(if|else|else-if)$/ 的東西。\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name)\n    ? (n: string) => n === name\n    : (n: string) => name.test(n)\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      // 只作用於 NodeTypes.ELEMENT\n      const { props } = node\n      const exitFns = []\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i]\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          // 為匹配名稱的 NodeTypes.DIRECTIVE 執行轉換器\n          props.splice(i, 1)\n          i--\n          const onExit = fn(node, prop, context)\n          if (onExit) exitFns.push(onExit)\n        }\n      }\n      return exitFns\n    }\n  }\n}\n```\n\n## 實現 v-if\n\n### AST 實現\n\n準備工作到此為止已經完成．從這裡開始，讓我們實現 v-if．\n\n像往常一樣，讓我們從 AST 的定義開始，實現解析器．\n\n我想說，但這次似乎我們不需要解析器．\n\n相反，這次我們將考慮我們希望轉換後的 AST 看起來如何，並實現轉換器來相應地轉換它．\n\n讓我們看看開始時假設的編譯程式碼．\n\n```ts\nfunction render(_ctx) {\n  with (_ctx) {\n    const {\n      toHandlerKey: _toHandlerKey,\n      normalizeProps: _normalizeProps,\n      createVNode: _createVNode,\n      createCommentVNode: _createCommentVNode,\n      Fragment: _Fragment,\n    } = ChibiVue\n\n    return _createVNode(_Fragment, null, [\n      _createVNode(\n        'button',\n        _normalizeProps({ [_toHandlerKey('click')]: inc }),\n        'inc',\n      ),\n      n % 5 === 0 && n % 3 === 0\n        ? _createVNode('p', null, 'FizzBuzz')\n        : n % 5 === 0\n          ? _createVNode('p', null, 'Buzz')\n          : n % 3 === 0\n            ? _createVNode('p', null, 'Fizz')\n            : _createVNode('p', null, n),\n    ])\n  }\n}\n```\n\n可以看出，它最終被轉換為條件表達式（三元運算符）．\n\n由於我們以前從未處理過條件表達式，似乎我們需要在 Codegen 的 AST 端處理這個．\n基本上，我們想要考慮三個資訊（因為它是\"三元\"運算符）．\n\n- **條件**\n  這是 A ? B : C 中對應於 A 的部分．\n  用名稱\"condition\"表示．\n- **條件匹配時的節點**\n  這是 A ? B : C 中對應於 B 的部分．\n  用名稱\"consequent\"表示．\n- **條件不匹配時的節點**\n  這是 A ? B : C 中對應於 C 的部分．\n  用名稱\"alternate\"表示．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  JS_CONDITIONAL_EXPRESSION,\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION\n  test: JSChildNode\n  consequent: JSChildNode\n  alternate: JSChildNode\n  newline: boolean\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n\nexport function createConditionalExpression(\n  test: ConditionalExpression['test'],\n  consequent: ConditionalExpression['consequent'],\n  alternate: ConditionalExpression['alternate'],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  }\n}\n```\n\n我們將使用這些實現一個 AST 來表示 VIf 節點．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  // .\n  IF,\n  IF_BRANCH,\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF\n  branches: IfBranchNode[]\n  codegenNode?: IfConditionalExpression\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall\n  alternate: VNodeCall | IfConditionalExpression\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH\n  condition: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  userKey?: AttributeNode | DirectiveNode\n}\n\nexport type ParentNode = RootNode | ElementNode | IfBranchNode\n```\n\n### 轉換器的實現\n\n現在我們有了 AST，讓我們實現生成此 AST 的轉換器．\n\n想法是基於幾個 `ElementNode` 生成一個 `IfNode`．\n\n所謂\"幾個\"，在這種情況下，意味著如果有多個 `ElementNode`，我們需要生成一個包含從 `v-if` 到 `v-else` 語句的單個 `IfNode`．\n\n如果第一個 `v-if` 匹配，我們需要在檢查後續節點是否為 `v-else-if` 或 `v-else` 的同時生成 `IfNode`．\n\n讓我們首先實現整體結構，使用我們之前實現的 `createStructuralDirectiveTransform`．\n\n具體來說，由於我們最終想要用我們之前實現的 AST 填充 `codegenNode`，我們將在此轉換器的 `onExit` 中生成 Node．\n\n```ts\nexport const transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!)\n          parentCondition.alternate = createCodegenNodeForBranch(\n            branch,\n            context,\n          )\n        }\n      }\n    })\n  },\n)\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // TODO:\n}\n```\n\n```ts\n/// 用於生成 codegenNode 的函式\n\n// 為分支生成 codegenNode\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, context),\n      // alternate 暫時設置為生成註釋。\n      // 當遇到 v-else-if 或 v-else 時，它將被替換為目標 Node。\n      // 這是寫 `parentCondition.alternate = createCodegenNodeForBranch(branch, context);` 的部分。\n      // 如果沒有遇到 v-else-if 或 v-else，它將保持為 CREATE_COMMENT Node。\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', 'true']),\n    ) as IfConditionalExpression\n  } else {\n    return createChildrenCodegenNode(branch, context)\n  }\n}\n\nfunction createChildrenCodegenNode(\n  branch: IfBranchNode,\n  context: TransformContext,\n): VNodeCall {\n  // 只是從分支中提取 vnode call\n  const { children } = branch\n  const firstChild = children[0]\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall\n  return vnodeCall\n}\n\nfunction getParentCondition(\n  node: IfConditionalExpression,\n): IfConditionalExpression {\n  // 透過從節點追蹤獲取結束 Node\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate\n      } else {\n        return node\n      }\n    }\n  }\n}\n```\n\n在 `processIf` 中，執行更具體的 AST 節點轉換．\n\n有 if / else-if / else 的情況，但讓我們首先考慮 `if` 的情況．\n\n這非常簡單．我們創建一個 IfNode 並執行 codegenNode 生成．\n此時，我們將當前 Node 生成為 IfBranch 並將其分配給 IfNode，然後用 IfNode 替換它．\n\n```\n- parent\n  - currentNode\n\n↓\n\n- parent\n  - IfNode\n    - IfBranch (currentNode)\n```\n\n這是改變結構的圖像．\n\n```ts\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n) {\n  // 我們將提前在 exp 上執行 processExpression。\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)\n  }\n\n  if (dir.name === 'if') {\n    const branch = createIfBranch(node, dir)\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    }\n    context.replaceNode(ifNode)\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true)\n    }\n  } else {\n    // TODO:\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === 'else' ? undefined : dir.exp,\n    children: [node],\n  }\n}\n```\n\n讓我們考慮除 v-if 之外的情況．\n\n我們將透過上下文從父級的子級遍歷以獲取兄弟節點．\n我們將循環遍歷節點（從當前節點本身開始）並基於自身生成 IfBranch，將它們推入分支．\n在此過程中，註釋和空文字將被刪除．\n\n```ts\nif (dir.name === 'if') {\n  /** 省略 */\n} else {\n  const siblings = context.parent!.children\n  let i = siblings.indexOf(node)\n  while (i-- >= -1) {\n    const sibling = siblings[i]\n    if (sibling && sibling.type === NodeTypes.COMMENT) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (\n      sibling &&\n      sibling.type === NodeTypes.TEXT &&\n      !sibling.content.trim().length\n    ) {\n      context.removeNode(sibling)\n      continue\n    }\n\n    if (sibling && sibling.type === NodeTypes.IF) {\n      context.removeNode()\n      const branch = createIfBranch(node, dir)\n      sibling.branches.push(branch)\n      const onExit = processCodegen && processCodegen(sibling, branch, false)\n      traverseNode(branch, context)\n      if (onExit) onExit()\n      context.currentNode = null\n    }\n    break\n  }\n}\n```\n\n如你所見，實際上 else-if 和 else 沒有區別．\n\n即使在 AST 中，如果沒有條件，它被定義為 else，所以沒有什麼特別需要考慮的．\n（在 `createIfBranch` 的 `dir.name === \"else\" ? undefined : dir.exp` 部分被吸收）\n\n重要的是在 `if` 時生成 `IfNode`，對於其他情況，只需將它們推入該 Node 的分支．\n\n透過這樣，transformIf 的實現就完成了．我們只需要在周圍進行一些調整．\n\n在 traverseNode 中，我們將為 IfNode 擁有的分支執行 traverseNode．\n\n我們還將 IfBranch 包含為 traverseChildren 的目標．\n\n```ts\nexport function traverseNode(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) {\n  // .\n  // .\n  // .\n  switch (node.type) {\n    // .\n    // .\n    // 添加\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context)\n      }\n      break\n\n    case NodeTypes.IF_BRANCH: // 添加\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n      traverseChildren(node, context)\n      break\n  }\n}\n```\n\n最後，我們只需要在編譯器中將 transformIf 註冊為選項．\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformElement],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\n透過這樣，轉換器就實現了！\n\n剩下的就是實現 codegen，v-if 就完成了．我們快到了，讓我們加油！\n\n### codegen 的實現\n\n剩下的很容易．只需基於 ConditionalExpression 的 Node 生成程式碼．\n\n```ts\nconst genNode = (\n  node: CodegenNode,\n  context: CodegenContext,\n  option: CompilerOptions,\n) => {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.IF: // 不要忘記添加這個！\n      genNode(node.codegenNode!, context, option)\n      break\n    // .\n    // .\n    // .\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context, option)\n      break\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break\n  }\n}\n\nfunction genConditionalExpression(\n  node: ConditionalExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { test, consequent, alternate, newline: needNewline } = node\n  const { push, indent, deindent, newline } = context\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context)\n  } else {\n    push(`(`)\n    genNode(test, context, option)\n    push(`)`)\n  }\n  needNewline && indent()\n  context.indentLevel++\n  needNewline || push(` `)\n  push(`? `)\n  genNode(consequent, context, option)\n  context.indentLevel--\n  needNewline && newline()\n  needNewline || push(` `)\n  push(`: `)\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION\n  if (!isNested) {\n    context.indentLevel++\n  }\n  genNode(alternate, context, option)\n  if (!isNested) {\n    context.indentLevel--\n  }\n  needNewline && deindent(true /* without newline */)\n}\n```\n\n像往常一樣，我們只是基於 AST 生成條件表達式，所以沒有什麼特別困難的．\n\n## 完成！！\n\n嗯，自從我們有一個稍微胖的章節以來已經有一段時間了，但透過這樣，v-if 的實現就完成了！（幹得好！）\n\n讓我們嘗試真正執行它！！\n\n它工作正常！\n\n![v-if FizzBuzz result in the browser](/figures/50-basic-template-compiler/v-if/fizzbuzz-result.png)\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/040_v_if_and_structural_directive)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/050-v-for.md",
    "content": "# 支援 v-for 指令\n\n## 目標開發者介面\n\n現在，讓我們繼續指令的實現．這次，讓我們嘗試支援 v-for．\n\n嗯，我想對於那些之前使用過 Vue.js 的人來說，這是一個熟悉的指令．\n\nv-for 有各種語法．\n最基本的是循環遍歷陣列，但你也可以循環遍歷其他東西，如字串，物件鍵，範圍等等．\n\nhttps://vuejs.org/v2/guide/list.html\n\n雖然有點長，但這次，讓我們以以下開發者介面為目標：\n\n```vue\n<script>\nimport { createApp, defineComponent, ref } from 'chibivue'\n\nconst genId = () => Math.random().toString(36).slice(2)\n\nconst FRUITS_FACTORIES = [\n  () => ({ id: genId(), name: 'apple', color: 'red' }),\n  () => ({ id: genId(), name: 'banana', color: 'yellow' }),\n  () => ({ id: genId(), name: 'grape', color: 'purple' }),\n]\n\nexport default {\n  setup() {\n    const fruits = ref([...FRUITS_FACTORIES].map(f => f()))\n    const addFruit = () => {\n      fruits.value.push(\n        FRUITS_FACTORIES[Math.floor(Math.random() * FRUITS_FACTORIES.length)](),\n      )\n    }\n    return { fruits, addFruit }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"addFruit\">add fruits!</button>\n\n  <!-- basic -->\n  <ul>\n    <li v-for=\"fruit in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- indexed -->\n  <ul>\n    <li v-for=\"(fruit, i) in fruits\" :key=\"fruit.id\">\n      <span :style=\"{ backgroundColor: fruit.color }\">{{ fruit.name }}</span>\n    </li>\n  </ul>\n\n  <!-- destructuring -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">{{ name }}</span>\n    </li>\n  </ul>\n\n  <!-- object -->\n  <ul>\n    <li v-for=\"(value, key, idx) in fruits[0]\" :key=\"key\">\n      [{{ idx }}] {{ key }}: {{ value }}\n    </li>\n  </ul>\n\n  <!-- range -->\n  <ul>\n    <li v-for=\"n in 10\">{{ n }}</li>\n  </ul>\n\n  <!-- string -->\n  <ul>\n    <li v-for=\"c in 'hello'\">{{ c }}</li>\n  </ul>\n\n  <!-- nested -->\n  <ul>\n    <li v-for=\"({ id, name, color }, i) in fruits\" :key=\"id\">\n      <span :style=\"{ backgroundColor: color }\">\n        <span v-for=\"n in 3\">{{ n }}</span>\n        <span>{{ name }}</span>\n      </span>\n    </li>\n  </ul>\n</template>\n```\n\n你可能會想，\"我們突然要實現這麼多東西？這不可能！\"但不要擔心，我會一步一步地解釋．\n\n## 實現方法\n\n首先，讓我們大致思考一下我們想要如何編譯它，並考慮在實現時可能遇到的困難點．\n\n首先，讓我們看看期望的編譯結果．\n\n基本結構並不那麼困難．我們將在 runtime-core 中實現一個名為 renderList 的輔助函式來渲染列表，並將其編譯為表達式．\n\n示例 1：\n\n```html\n<!-- input -->\n<li v-for=\"fruit in fruits\" :key=\"fruit.id\">{{ fruit.name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\n示例 2：\n\n```html\n<!-- input -->\n<li v-for=\"(fruit, idx) in fruits\" :key=\"fruit.id\">\n  {{ idx }}: {{ fruit.name }}\n</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, fruit => h('li', { key: fruit.id }, fruit.name)),\n)\n```\n\n示例 3：\n\n```html\n<!-- input -->\n<li v-for=\"{ name, id } in fruits\" :key=\"id\">{{ name }}</li>\n```\n\n```ts\n// output\nh(\n  _Fragment,\n  null,\n  _renderList(fruits, ({ name, id }) => h('li', { key: id }, name)),\n)\n```\n\n將來，作為 renderList 第一個參數傳遞的值預期不僅是陣列，還可能是數字和物件．但是，現在讓我們假設只期望陣列．\\_renderList 函式本身的實現可以理解為類似於 Array.prototype.map 的東西．至於除陣列之外的值，你只需要在 \\_renderList 中對它們進行規範化，所以現在讓我們忘記它們（只關注陣列）．\n\n現在，對於那些到目前為止已經實現了各種指令的人來說，實現這種編譯器（轉換器）應該不會太困難．\n\n## 關鍵實現點（困難點）\n\n困難點在於在 SFC（單檔案組件）中使用它時．你還記得在 SFC 中使用的編譯器和在瀏覽器中使用的編譯器之間的區別嗎？是的，就是使用 `_ctx` 解析表達式．\n\n在 v-for 中，使用者定義的局部變數以各種形式出現，所以你需要正確地收集它們並跳過 rewriteIdentifiers．\n\n```ts\n// 錯誤示例\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // fruits 有前綴是可以的，因為它是從 _ctx 綁定的\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: _ctx.id }, // 這裡有 _ctx 是不對的\n        _ctx.name, // 這裡有 _ctx 是不對的\n      ),\n  ),\n)\n```\n\n```ts\n// 正確示例\nh(\n  _Fragment,\n  null,\n  _renderList(\n    _ctx.fruits, // fruits 有前綴是可以的，因為它是從 _ctx 綁定的\n    ({ name, id }) =>\n      h(\n        'li',\n        { key: id }, // 這裡不應該有 _ctx\n        name, // 這裡不應該有 _ctx\n      ),\n  ),\n)\n```\n\n從示例 1 到 3，有各種局部變數的定義．\n\n你需要分析每個定義並收集要跳過的識別符．\n\n現在，讓我們暫時擱置如何實現這一點，從大局開始實現．\n\n## AST 的實現\n\n現在，讓我們像往常一樣定義 AST．\n\n與 v-if 一樣，我們將考慮轉換後的 AST（無需實現解析器）．\n\n```ts\nexport const enum NodeTypes {\n  // .\n  // .\n  FOR, // [!code ++]\n  // .\n  // .\n  JS_FUNCTION_EXPRESSION, // [!code ++]\n}\n\nexport type ParentNode =\n  | RootNode\n  | ElementNode\n  | ForNode // [!code ++]\n  | IfBranchNode\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR\n  source: ExpressionNode\n  valueAlias: ExpressionNode | undefined\n  keyAlias: ExpressionNode | undefined\n  children: TemplateChildNode[]\n  parseResult: ForParseResult // 稍後解釋\n  codegenNode?: ForCodegenNode\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true\n  tag: typeof FRAGMENT\n  props: undefined\n  children: ForRenderListExpression\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST // 稍後解釋\n  arguments: [ExpressionNode, ForIteratorExpression]\n}\n\n// 還支援函式表達式，因為回呼函式用作 renderList 的第二個參數。\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n}\n\n// 在 v-for 的情況下，返回是固定的，所以它被表示為專門用於此目的的 AST。\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall\n}\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ConditionalExpression\n  | ExpressionNode\n  | FunctionExpression // [!code ++]\n```\n\n關於 `RENDER_LIST`，像往常一樣，將其添加到 `runtimeHelpers`．\n\n```ts\n// runtimeHelpers.ts\n// .\n// .\n// .\nexport const RENDER_LIST = Symbol() // [!code ++]\n\nexport const helperNameMap: Record<symbol, string> = {\n  // .\n  // .\n  [RENDER_LIST]: `renderList`, // [!code ++]\n  // .\n  // .\n}\n```\n\n至於 `ForParseResult`，其定義在 `transform/vFor` 中．\n\n```ts\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n```\n\n為了解釋它們各自指的是什麼，\n\n在 `v-for=\"(fruit, i) in fruits\"` 的情況下，\n\n- source: `fruits`\n- value: `fruit`\n- key: `i`\n- index: `undefined`\n\n`index` 是將物件應用於 `v-for` 時的第三個參數．\n\nhttps://vuejs.org/v2/guide/list.html#v-for-with-an-object\n\n![ForParseResult shape](/figures/50-basic-template-compiler/v-for/for-parse-result.svg)\n\n關於 `value`，如果你使用像 `{ id, name, color, }` 這樣的解構賦值，它將有多個識別符．\n\n我們收集由 `value`，`key` 和 `index` 定義的識別符，並跳過添加前綴．\n\n## codegen 的實現\n\n雖然順序有點顛倒，但讓我們先實現 codegen，因為沒有太多要討論的．\n只需要做兩件事：處理 `NodeTypes.FOR` 和函式表達式的 codegen（這是第一次出現）．\n\n```ts\nswitch (node.type) {\n  case NodeTypes.ELEMENT:\n  case NodeTypes.FOR: // [!code ++]\n  case NodeTypes.IF:\n  // .\n  // .\n  // .\n  case NodeTypes.JS_FUNCTION_EXPRESSION: // [!code ++]\n    genFunctionExpression(node, context, option) // [!code ++]\n    break // [!code ++]\n  // .\n  // .\n  // .\n}\n\nfunction genFunctionExpression(\n  node: FunctionExpression,\n  context: CodegenContext,\n  option: CompilerOptions,\n) {\n  const { push, indent, deindent } = context\n  const { params, returns, newline } = node\n\n  push(`(`, node)\n  if (isArray(params)) {\n    genNodeList(params, context, option)\n  } else if (params) {\n    genNode(params, context, option)\n  }\n  push(`) => `)\n  if (newline) {\n    push(`{`)\n    indent()\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `)\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context, option)\n    } else {\n      genNode(returns, context, option)\n    }\n  }\n  if (newline) {\n    deindent()\n    push(`}`)\n  }\n}\n```\n\n沒有什麼特別困難的．就這樣結束了．\n\n## 轉換器的實現\n\n### 準備工作\n\n在實現轉換器之前，還有一些準備工作．\n\n正如我們在 `v-on` 中所做的，在 `v-for` 的情況下，執行 `processExpression` 的時機有點特殊（我們需要收集局部變數），所以我們在 `transformExpression` 中跳過它．\n\n```ts\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx)\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i]\n      if (\n        dir.type === NodeTypes.DIRECTIVE &&\n        dir.name !== 'for' // [!code ++]\n      ) {\n        // .\n        // .\n        // .\n      }\n    }\n  }\n}\n```\n\n### 收集識別符\n\n現在，在我們繼續主要實現之前，讓我們思考如何收集識別符．\n\n這次，我們需要考慮不僅是像 `fruit` 這樣的簡單識別符，還有像 `{ id, name, color }` 這樣的解構賦值．\n為此，似乎我們需要像往常一樣使用 TreeWalker．\n\n目前，在 `processExpression` 函式中，實現是搜尋識別符並向它們添加 `_ctx`．但是，這次我們只需要收集識別符而不添加任何東西．讓我們實現這一點．\n\n首先，讓我們準備一個地方來存儲收集的識別符．由於如果每個 Node 都有它們對於 codegen 和其他目的會很方便，讓我們向 AST 添加一個可以在每個 Node 上保存多個識別符的屬性．\n\n目標是 `CompoundExpressionNode` 和 `SimpleExpressionNode`．\n\n像 `fruit` 這樣的簡單識別符將被添加到 `SimpleExpressionNode`，\n像 `{ id, name, color }` 這樣的解構賦值將被添加到 `CompoundExpressionNode`．（在視覺化方面，它將是一個複合表達式，如 `[\"{\", simpleExpr(\"id\"), \",\", simpleExpr(\"name\"), \",\", simpleExpr(\"color\"), \"}\"]`）\n\n```ts\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION\n  content: string\n  isStatic: boolean\n  identifiers?: string[] // [!code ++]\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n  )[]\n  identifiers?: string[] // [!code ++]\n}\n```\n\n在 `processExpression` 函式中，讓我們在這裡實現收集識別符的邏輯，並透過將收集的識別符添加到轉換器的上下文中來跳過添加前綴．\n\n目前，用於添加/刪除識別符的函式被配置為接收單個識別符作為字串，所以讓我們將其更改為假設 `{ identifier: string[] }` 的形式．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  // .\n  // .\n  addIdentifiers(exp: ExpressionNode | string): void\n  removeIdentifiers(exp: ExpressionNode | string): void\n  // .\n  // .\n  // .\n}\n\nconst context: TransformContext = {\n  // .\n  // .\n  // .\n  addIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        addId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(addId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        addId(exp.content)\n      }\n    }\n  },\n  removeIdentifiers(exp) {\n    if (!isBrowser) {\n      if (isString(exp)) {\n        removeId(exp)\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(removeId)\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        removeId(exp.content)\n      }\n    }\n  },\n  // .\n  // .\n  // .\n}\n```\n\n現在，讓我們在 `processExpression` 函式中實現收集識別符的邏輯．\n\n在 `processExpression` 函式中，定義一個名為 `asParams` 的選項，如果設置為 true，實現跳過添加前綴並在 `node.identifiers` 中收集識別符的邏輯．\n\n`asParams` 旨在引用在 `renderList` 的回呼函式中定義的參數（局部變數）．\n\n```ts\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false, // [!code ++]\n) {\n  // .\n  if (isSimpleIdentifier(rawExp)) {\n    const isScopeVarReference = ctx.identifiers[rawExp]\n    if (\n      !asParams && // [!code ++]\n      !isScopeVarReference\n    ) {\n      node.content = rewriteIdentifier(rawExp)\n    } // [!code ++]\n    return node\n\n    // .\n  }\n}\n```\n\n這就是簡單識別符的結束．問題在於其他情況．\n\n為此，我們將使用在 `babelUtils` 中實現的 `walkIdentifiers`．\n\n由於我們假設定義為函式參數的局部變數，我們將在此函式中將它們轉換為\"函式參數\"，並在 `walkIdentifier` 中將它們作為 Function params 搜尋．\n\n```ts\n// 將 asParams 轉換為類似函式參數的形式\nconst source = `(${rawExp})${asParams ? `=>{}` : ``}`\n\n// walkIdentifiers 稍微複雜一些。\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n) {\n  // .\n\n  ;(walk as any)(root, {\n    // prettier-ignore\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n\n      } else if (isFunctionType(node)) {\n        // 稍後解釋（在此函式內的 knownIds 中收集識別符）\n        walkFunctionParams(node, (id) =>\n          markScopeIdentifier(node, id, knownIds)\n        );\n      }\n    },\n  })\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)\n}\n```\n\n我們在這裡做的只是如果 node 是函式則遍歷參數，並將識別符收集到 `identifiers` 中．\n\n在 `walkIdentifiers` 的呼叫者中，我們定義 `knownIds` 並將其與 `knownIds` 一起傳遞給 `walkIdentifiers` 以收集識別符．\n\n在 `walkIdentifiers` 中收集後，最後，在生成 CompoundExpression 時基於 `knownIds` 生成識別符．\n\n```ts\nconst knownIds: Record<string, number> = Object.create(ctx.identifiers)\n\nwalkIdentifiers(\n  ast,\n  node => {\n    node.name = rewriteIdentifier(node.name)\n    ids.push(node as QualifiedId)\n  },\n  knownIds, // 傳遞\n  parentStack,\n)\n\n// .\n// .\n// .\n\nret.identifiers = Object.keys(knownIds) // 基於 knownIds 生成識別符\nreturn ret\n```\n\n雖然檔案有點亂序，但 `walkFunctionParams` 和 `markScopeIdentifier` 只是遍歷參數並將 `Node.name` 添加到 `knownIds`．\n\n```ts\nexport function walkFunctionParams(\n  node: Function,\n  onIdent: (id: Identifier) => void,\n) {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id)\n    }\n  }\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return\n  }\n  if (name in knownIds) {\n    knownIds[name]++\n  } else {\n    knownIds[name] = 1\n  }\n  ;(node.scopeIds || (node.scopeIds = new Set())).add(name)\n}\n```\n\n有了這個，我們應該能夠收集識別符．讓我們使用這個實現 `transformFor` 並完成 v-for 指令！\n\n### transformFor\n\n現在我們已經克服了障礙，讓我們像往常一樣使用我們擁有的東西實現轉換器．\n還有一點點，讓我們加油！\n\n像 v-if 一樣，這也涉及結構，所以讓我們使用 `createStructuralDirectiveTransform` 來實現它．\n\n我認為如果我用程式碼寫解釋會更容易理解，所以我將在下面提供帶有解釋的程式碼．但是，請在查看這個之前嘗試透過閱讀原始碼自己實現它！\n\n```ts\n// 這是主要結構的實現，類似於 v-if。\n// 它在適當的地方執行 processFor 並在適當的地方生成 codegenNode。\n// processFor 是最複雜的實現。\nexport const transformFor = createStructuralDirectiveTransform(\n  'for',\n  (node, dir, context) => {\n    return processFor(node, dir, context, forNode => {\n      // 如預期的那樣，生成呼叫 renderList 的程式碼。\n      const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n        forNode.source,\n      ]) as ForRenderListExpression\n\n      // 為作為 v-for 容器的 Fragment 生成 codegenNode。\n      forNode.codegenNode = createVNodeCall(\n        context,\n        context.helper(FRAGMENT),\n        undefined,\n        renderExp,\n      ) as ForCodegenNode\n\n      // codegen 過程（在 processFor 中的解析和識別符收集之後執行）\n      return () => {\n        const { children } = forNode\n        const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall\n\n        renderExp.arguments.push(\n          createFunctionExpression(\n            createForLoopParams(forNode.parseResult),\n            childBlock,\n            true /* force newline */,\n          ) as ForIteratorExpression,\n        )\n      }\n    })\n  },\n)\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n) {\n  // 解析 v-for 的表達式。\n  // 在 parseResult 階段，每個 Node 的識別符已經被收集。\n  const parseResult = parseForExpression(\n    dir.exp as SimpleExpressionNode,\n    context,\n  )\n\n  const { addIdentifiers, removeIdentifiers } = context\n\n  const { source, value, key, index } = parseResult!\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  }\n\n  // 用 forNode 替換 Node。\n  context.replaceNode(forNode)\n\n  if (!context.isBrowser) {\n    // 將收集的識別符添加到上下文中。\n    value && addIdentifiers(value)\n    key && addIdentifiers(key)\n    index && addIdentifiers(index)\n  }\n\n  // 生成程式碼（這允許跳過向局部變數添加前綴）\n  const onExit = processCodegen && processCodegen(forNode)\n\n  return () => {\n    value && removeIdentifiers(value)\n    key && removeIdentifiers(key)\n    index && removeIdentifiers(index)\n\n    if (onExit) onExit()\n  }\n}\n\n// 使用正規表達式解析給定給 v-for 的表達式。\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/\nconst stripParensRE = /^\\(|\\)$/g\n\nexport interface ForParseResult {\n  source: ExpressionNode\n  value: ExpressionNode | undefined\n  key: ExpressionNode | undefined\n  index: ExpressionNode | undefined\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc\n  const exp = input.content\n  const inMatch = exp.match(forAliasRE)\n\n  if (!inMatch) return\n\n  const [, LHS, RHS] = inMatch\n  const result: ForParseResult = {\n    source: createAliasExpression(\n      loc,\n      RHS.trim(),\n      exp.indexOf(RHS, LHS.length),\n    ),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  }\n\n  if (!context.isBrowser) {\n    result.source = processExpression(\n      result.source as SimpleExpressionNode,\n      context,\n    )\n  }\n\n  let valueContent = LHS.trim().replace(stripParensRE, '').trim()\n  const iteratorMatch = valueContent.match(forIteratorRE)\n  const trimmedOffset = LHS.indexOf(valueContent)\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, '').trim()\n    const keyContent = iteratorMatch[1].trim()\n    let keyOffset: number | undefined\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)\n      result.key = createAliasExpression(loc, keyContent, keyOffset)\n      if (!context.isBrowser) {\n        // 如果不在瀏覽器模式下，將 asParams 設置為 true 並收集 key 的識別符。\n        result.key = processExpression(result.key, context, true)\n      }\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim()\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key\n              ? keyOffset! + keyContent.length\n              : trimmedOffset + valueContent.length,\n          ),\n        )\n        if (!context.isBrowser) {\n          // 如果不在瀏覽器模式下，將 asParams 設置為 true 並收集 index 的識別符。\n          result.index = processExpression(result.index, context, true)\n        }\n      }\n    }\n  }\n\n  if (valueContent) {\n    result.value = createAliasExpression(loc, valueContent, trimmedOffset)\n    if (!context.isBrowser) {\n      // 如果不在瀏覽器模式下，將 asParams 設置為 true 並收集 value 的識別符。\n      result.value = processExpression(result.value, context, true)\n    }\n  }\n\n  return result\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(\n    content,\n    false,\n    getInnerRange(range, offset, content.length),\n  )\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs])\n}\n\nfunction createParamsList(\n  args: (ExpressionNode | undefined)[],\n): ExpressionNode[] {\n  let i = args.length\n  while (i--) {\n    if (args[i]) break\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))\n}\n```\n\n現在，剩下的部分是實際包含在編譯程式碼中的 renderList 的實現，以及註冊轉換器的實現．如果我們能實現這些，v-for 應該就能工作了！\n\n讓我們嘗試執行它！\n\n![v-for result in the browser](/figures/50-basic-template-compiler/v-for/v-for-result.png)\n\n看起來進展順利．\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/050_v_for)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/070-resolve-component.md",
    "content": "# 解析組件\n\n實際上，我們的 chibivue 模板還無法解析組件．\n讓我們在這裡實現它，因為 Vue.js 提供了幾種解析組件的方法．\n\n首先，讓我們回顧一些解析方法．\n\n## 組件的解析方法\n\n### 1. Components 選項（局部註冊）\n\n這可能是解析組件最簡單的方法．\n\nhttps://vuejs.org/api/options-misc.html#components\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: {\n    MyComponent,\n    MyComponent2: MyComponent,\n  },\n}\n</script>\n\n<template>\n  <MyComponent />\n  <MyComponent2 />\n</template>\n```\n\n在 components 選項物件中指定的鍵名成為可以在模板中使用的組件名稱．\n\n### 2. 在應用程式上註冊（全域註冊）\n\n您可以透過使用建立的 Vue 應用程式的 `.component()` 方法來註冊可在整個應用程式中使用的組件．\n\nhttps://vuejs.org/guide/components/registration.html#global-registration\n\n```ts\nimport { createApp } from 'vue'\n\nconst app = createApp({})\n\napp\n  .component('ComponentA', ComponentA)\n  .component('ComponentB', ComponentB)\n  .component('ComponentC', ComponentC)\n```\n\n### 3. 動態組件 + is 屬性\n\n透過使用 is 屬性，您可以動態切換組件．\n\nhttps://vuejs.org/api/built-in-special-elements.html#component\n\n```vue\n<script>\nimport Foo from './Foo.vue'\nimport Bar from './Bar.vue'\n\nexport default {\n  components: { Foo, Bar },\n  data() {\n    return {\n      view: 'Foo',\n    }\n  },\n}\n</script>\n\n<template>\n  <component :is=\"view\" />\n</template>\n```\n\n### 4. 在 script setup 中匯入\n\n在 script setup 中，您可以直接使用匯入的組件．\n\n```vue\n<script setup>\nimport MyComponent from './MyComponent.vue'\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n此外，還有非同步組件，嵌入式組件和 `component` 標籤，但這次我將嘗試處理上述兩種（1，2）．\n\n關於 3，如果 1 和 2 可以處理它，那只是一個擴展．至於 4，由於 script setup 尚未實現，我們將暫時擱置．\n\n## 基本方法\n\n解析組件的基本方法如下：\n\n- 在某個地方，儲存模板中使用的名稱和組件記錄．\n- 使用輔助函式根據名稱解析組件．\n\n形式 1 和形式 2 都只是儲存名稱和組件記錄，唯一的區別是它們註冊的位置．  \n如果您有記錄，您可以在必要時從名稱解析組件，因此兩種實現都將類似．\n\n首先，讓我們看一下預期的程式碼和編譯結果．\n\n```vue\n<script>\nimport MyComponent from './MyComponent.vue'\n\nexport default defineComponent({\n  components: { MyComponent },\n})\n</script>\n\n<template>\n  <MyComponent />\n</template>\n```\n\n```js\n// 編譯結果\n\nfunction render(_ctx) {\n  const {\n    resolveComponent: _resolveComponent,\n    createVNode: _createVNode,\n    Fragment: _Fragment,\n  } = ChibiVue\n\n  const _component_MyComponent = _resolveComponent('MyComponent')\n\n  return _createVNode(_Fragment, null, _createVNode(_component_MyComponent))\n}\n```\n\n看起來是這樣的．\n\n## 實現\n\n### AST\n\n為了產生解析組件的程式碼，我們需要知道\"MyComponent\"是一個組件．  \n在解析階段，我們處理標籤名稱並在 AST 上將其分為常規 Element 和 Component．\n\n首先，讓我們考慮 AST 的定義．  \nComponentNode 與常規 Element 一樣，具有 props 和 children．  \n在將這些公共部分合併為 `BaseElementNode` 的同時，我們將現有的 `ElementNode` 重新命名為 `PlainElementNode`，  \n並使 `ElementNode` 成為 `PlainElementNode` 和 `ComponentNode` 的聯合．\n\n```ts\n// compiler-core/ast.ts\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT\n  tag: string\n  tagType: ElementTypes\n  isSelfClosing: boolean\n  props: Array<AttributeNode | DirectiveNode>\n  children: TemplateChildNode[]\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT\n  codegenNode: VNodeCall | undefined\n}\n```\n\n內容與之前相同，但我們透過 `tagType` 區分它們並將它們視為單獨的 AST．  \n我們將在轉換階段使用它來新增輔助函式等．\n\n### 解析器\n\n接下來，讓我們實現解析器來產生上述 AST．  \n基本上，我們只需要根據標籤名稱確定 `tagType`．\n\n問題是如何確定它是 Element 還是 Component．\n\n基本思路很簡單：只需確定它是否是\"原生標籤\"．\n\n・  \n・  \n・\n\n\"等等，等等，這不是我要問的．我們實際上如何實現它？\"\n\n是的，這是一種暴力方法．我們預定義原生標籤名稱清單並確定它是否匹配．  \n至於應該枚舉哪些項目，所有這些都應該寫在規範中，所以我們將信任它並使用它．\n\n如果有問題的話，\"什麼是原生標籤\"可能因環境而異．  \n在這種情況下，它是瀏覽器．我的意思是\"compiler-core 不應該依賴於環境\"．  \n到目前為止，我們已經在 compiler-dom 中實現了這樣的 DOM 依賴實現，這個枚舉也不例外．\n\n考慮到這一點，我們將實現它，以便可以從解析器外部注入\"是否為原生標籤\"的函式作為選項，考慮到未來的可能性並使其易於在以後新增各種選項．\n\n```ts\ntype OptionalOptions = 'isNativeTag' // | TODO: Add more in the future (maybe)\n\ntype MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &\n  Pick<ParserOptions, OptionalOptions>\n\nexport interface ParserContext {\n  // .\n  // .\n  options: MergedParserOptions // [!code ++]\n  // .\n  // .\n}\n\nfunction createParserContext(\n  content: string,\n  rawOptions: ParserOptions, // [!code ++]\n): ParserContext {\n  const options = Object.assign({}, defaultParserOptions) // [!code ++]\n\n  let key: keyof ParserOptions // [!code ++]\n  // prettier-ignore\n  for (key in rawOptions) { // [!code ++]\n    options[key] = // [!code ++]\n      rawOptions[key] === undefined // [!code ++]\n        ? defaultParserOptions[key] // [!code ++]\n        : rawOptions[key]; // [!code ++]\n  } // [!code ++]\n\n  // .\n  // .\n  // .\n}\n\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {}, // [!code ++]\n): RootNode => {\n  const context = createParserContext(\n    content,\n    options, // [!code ++]\n  )\n  const children = parseChildren(context, [])\n  return createRoot(children)\n}\n```\n\n現在，在 compiler-dom 中，我們將枚舉原生標籤名稱並將它們作為選項傳遞．\n\n雖然我提到了 compiler-dom，但枚舉本身是在 shared/domTagConfig.ts 中完成的．\n\n```ts\nimport { makeMap } from './makeMap'\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +\n  'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +\n  'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +\n  'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +\n  'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +\n  'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +\n  'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +\n  'option,output,progress,select,textarea,details,dialog,menu,' +\n  'summary,template,blockquote,iframe,tfoot'\n\nexport const isHTMLTag = makeMap(HTML_TAGS)\n```\n\n看起來相當可怕，不是嗎？\n\n但這是正確的實現．\n\nhttps://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/shared/src/domTagConfig.ts#L6\n\n建立 compiler-dom/parserOptions.ts 並將其傳遞給編譯器．\n\n```ts\n// compiler-dom/parserOptions.ts\n\nimport { ParserOptions } from '../compiler-core'\nimport { isHTMLTag, isSVGTag } from '../shared/domTagConfig'\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),\n}\n```\n\n```ts\nexport function compile(template: string, option?: CompilerOptions) {\n  const defaultOption = { isBrowser: true }\n  if (option) Object.assign(defaultOption, option)\n  return baseCompile(\n    template,\n    Object.assign(\n      {},\n      parserOptions, // [!code ++]\n      defaultOption,\n      {\n        directiveTransforms: DOMDirectiveTransforms,\n      },\n    ),\n  )\n}\n```\n\n解析器的實現已完成，所以我們現在將繼續實現其餘部分．\n\n其餘部分非常簡單．我們只需要確定它是否是組件並分配一個 tagType．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // .\n  // .\n  let tagType = ElementTypes.ELEMENT // [!code ++]\n  // prettier-ignore\n  if (isComponent(tag, context)) { // [!code ++]\n    tagType = ElementTypes.COMPONENT;// [!code ++]\n  } // [!code ++]\n\n  return {\n    // .\n    tagType, // [!code ++]\n    // .\n  }\n}\n\nfunction isComponent(tag: string, context: ParserContext) {\n  const options = context.options\n  if (\n    // NOTE: 在 Vue.js 中，以大寫字母開頭的標籤被視為組件。\n    // ref: https://github.com/vuejs/core/blob/32bdc5d1900ceb8df1e8ee33ea65af7b4da61051/packages/compiler-core/src/parse.ts#L662\n    /^[A-Z]/.test(tag) ||\n    (options.isNativeTag && !options.isNativeTag(tag))\n  ) {\n    return true\n  }\n}\n```\n\n有了這個，解析器和 AST 就完成了．我們現在將繼續使用這些來實現轉換和程式碼產生．\n\n### 轉換\n\n在轉換中需要做的事情非常簡單．\n\n在 transformElement 中，如果 Node 是 ComponentNode，我們只需要進行輕微的轉換．\n\n此時，我們還在上下文中註冊組件．\n這樣做是為了我們可以在程式碼產生期間集體解析它．\n如後面提到的，組件將在程式碼產生中作為資產集體解析．\n\n```ts\n// compiler-core/transforms/transformElement.ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    // .\n    // .\n\n    const isComponent = node.tagType === ElementTypes.COMPONENT // [!code ++]\n\n    const vnodeTag = isComponent // [!code ++]\n      ? resolveComponentType(node as ComponentNode, context) // [!code ++]\n      : `\"${tag}\"` // [!code ++]\n\n    // .\n    // .\n  }\n}\n\nfunction resolveComponentType(node: ComponentNode, context: TransformContext) {\n  let { tag } = node\n  context.helper(RESOLVE_COMPONENT)\n  context.components.add(tag) // 稍後解釋\n  return toValidAssetId(tag, `component`)\n}\n```\n\n```ts\n// util.ts\nexport function toValidAssetId(\n  name: string,\n  type: 'component', // | TODO:\n): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === '-' ? '_' : name.charCodeAt(replaceValue).toString()\n  })}`\n}\n```\n\n我們還確保在上下文中註冊它．\n\n```ts\nexport interface TransformContext extends Required<TransformOptions> {\n  // .\n  components: Set<string> // [!code ++]\n  // .\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  {\n    nodeTransforms = [],\n    directiveTransforms = {},\n    isBrowser = false,\n  }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    // .\n    components: new Set(), // [!code ++]\n    // .\n  }\n}\n```\n\n然後，上下文中的所有組件都在目標組件的 RootNode 中註冊．\n\n```ts\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT\n  children: TemplateChildNode[]\n  codegenNode?: TemplateChildNode | VNodeCall\n  helpers: Set<symbol>\n  components: string[] // [!code ++]\n}\n```\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions) {\n  const context = createTransformContext(root, options)\n  traverseNode(root, context)\n  createRootCodegen(root, context)\n  root.helpers = new Set([...context.helpers.keys()])\n  root.components = [...context.components] // [!code ++]\n}\n```\n\n有了這個，剩下的就是在程式碼產生中使用 RootNode.components．\n\n### 程式碼產生\n\n程式碼只是透過將名稱傳遞給輔助函式來產生程式碼以進行解析，就像我們在開始時看到的編譯結果一樣．我們將其抽象為\"資產\"以供將來考慮．\n\n```ts\nexport const generate = (ast: RootNode, option: CompilerOptions): string => {\n  // .\n  // .\n  genFunctionPreamble(ast, context) // NOTE: 將來將此移到函式外部\n\n  // prettier-ignore\n  if (ast.components.length) { // [!code ++]\n    genAssets(ast.components, \"component\", context); // [!code ++]\n    newline(); // [!code ++]\n    newline(); // [!code ++]\n  } // [!code ++]\n\n  push(`return `)\n  // .\n  // .\n}\n\nfunction genAssets(\n  assets: string[],\n  type: 'component' /* TODO: */,\n  { helper, push, newline }: CodegenContext,\n) {\n  if (type === 'component') {\n    const resolver = helper(RESOLVE_COMPONENT)\n    for (let i = 0; i < assets.length; i++) {\n      let id = assets[i]\n\n      push(\n        `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(\n          id,\n        )})`,\n      )\n      if (i < assets.length - 1) {\n        newline()\n      }\n    }\n  }\n}\n```\n\n### runtime-core 端的實現\n\n現在我們已經產生了所需的程式碼，讓我們轉到 runtime-core 中的實現．\n\n#### 為組件新增\"component\"作為選項\n\n這很簡單，只需將其新增到選項中．\n\n```ts\nexport type ComponentOptions<\n  // .\n  // .\n> = {\n  // .\n  components?: Record<string, Component>\n  // .\n}\n```\n\n#### 為應用程式新增\"components\"作為選項\n\n這也很簡單．\n\n```ts\nexport interface AppContext {\n  // .\n  components: Record<string, Component> // [!code ++]\n  // .\n}\n\nexport function createAppContext(): AppContext {\n  return {\n    // .\n    components: {}, // [!code ++]\n    // .\n  }\n}\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    // .\n    const app: App = (context.app = {\n      // .\n      // prettier-ignore\n      component(name: string, component: Component): any { // [!code ++]\n        context.components[name] = component; // [!code ++]\n        return app; // [!code ++]\n      },\n    })\n  }\n}\n```\n\n#### 實現從上述兩者解析組件的函式\n\n這裡沒有什麼特別需要解釋的．\n它搜尋本地和全域註冊的組件，並回傳組件．\n如果找不到，它將名稱原樣回傳作為回退．\n\n```ts\n// runtime-core/helpers/componentAssets.ts\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  const instance = currentInstance || currentRenderingInstance // 稍後解釋\n  if (instance) {\n    const Component = instance.type\n    const res =\n      // 本地註冊\n      resolve((Component as ComponentOptions).components, name) ||\n      // 全域註冊\n      resolve(instance.appContext.components, name)\n    return res\n  }\n\n  return name\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry &&\n    (registry[name] ||\n      registry[camelize(name)] ||\n      registry[capitalize(camelize(name))])\n  )\n}\n```\n\n需要注意的一點是 `currentRenderingInstance`．\n\n為了在 `resolveComponent` 中遍歷本地註冊的組件，我們需要存取目前正在渲染的組件．\n（我們想要搜尋正在渲染的組件的 `components` 選項）\n\n考慮到這一點，讓我們準備 `currentRenderingInstance` 並在渲染時更新它．\n\n```ts\n// runtime-core/componentRenderContexts.ts\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance\n  currentRenderingInstance = instance\n  return prev\n}\n```\n\n```ts\n// runtime-core/renderer.ts\n\nconst setupRenderEffect = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n  anchor: RendererElement | null,\n) => {\n  const componentUpdateFn = () => {\n    // .\n    // .\n    const prev = setCurrentRenderingInstance(instance) // [!code ++]\n    const subTree = (instance.subTree = normalizeVNode(render(proxy!))) // [!code ++]\n    setCurrentRenderingInstance(prev) // [!code ++]\n    // .\n    // .\n  }\n  // .\n  // .\n}\n```\n\n## 讓我們試試看\n\n太好了！我們終於可以解析組件了．\n\n讓我們嘗試在 playground 中執行它！\n\n```ts\nimport { createApp } from 'chibivue'\n\nimport App from './App.vue'\nimport Counter from './components/Counter.vue'\n\nconst app = createApp(App)\napp.component('GlobalCounter', Counter)\napp.mount('#app')\n```\n\nApp.vue\n\n```vue\n<script>\nimport Counter from './components/Counter.vue'\n\nimport { defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  components: { Counter },\n})\n</script>\n\n<template>\n  <Counter />\n  <Counter />\n  <GlobalCounter />\n</template>\n```\n\ncomponents/Counter.vue\n\n```vue\n<script>\nimport { ref, defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n})\n</script>\n\n<template>\n  <button @click=\"count++\">count: {{ count }}</button>\n</template>\n```\n\n![resolveComponent result in the browser](/figures/50-basic-template-compiler/resolve-component/resolve-components-result.png)\n\n看起來工作正常！太好了！\n\n到此為止的原始碼：[GitHub](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/060_resolve_components)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/080-component-slot-outlet.md",
    "content": "# 組件插槽\n\n## 期望的開發者介面\n\n我們已經有了基本組件系統插槽實現的執行時實現．\\\n但是，我們仍然無法在模板中處理插槽．\n\n我們希望處理如下的 SFC：\\\n（雖然我們說 SFC，但實際上是模板編譯器的實現．）\n\n```vue\n<!-- Comp.vue -->\n<template>\n  <p><slot name=\"default\" /></p>\n</template>\n```\n\n```vue\n<!-- App.vue -->\n<script>\nimport Comp from './Comp.vue'\nexport default {\n  components: {\n    Comp,\n  },\n  setup() {\n    const count = ref(0)\n    return { count }\n  },\n}\n</script>\n\n<template>\n  <Comp>\n    <template #default>\n      <button @click=\"count++\">count is: {{ count }}</button>\n    </template>\n  </Comp>\n</template>\n```\n\nVue.js 中有幾種類型的插槽：\n\n- 預設插槽\n- 具名插槽\n- 作用域插槽\n\n但是，正如您可能已經從執行時實現中了解到的，這些都只是回呼函式．讓我們回顧一下以防萬一．\n\n像上面這樣的組件被轉換為如下的渲染函式．\n\n```js\nh(Comp, null, {\n  default: () =>\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n})\n\n```\n\n在模板中，`name=\"default\"` 屬性可以省略，但在執行時，它仍然會被視為名為 `default` 的插槽．我們將在完成具名插槽的實現後實現預設插槽的編譯器．\n\n## 實現編譯器（插槽定義）\n\n像往常一樣，我們將實現解析和程式碼產生過程，但這次我們將處理插槽定義和插槽插入．\n\n首先，讓我們專注於插槽定義．這是在子組件端表示為 `<slot name=\"my-slot\"/>` 的部分．\n\n在執行時，我們將準備一個名為 `renderSlot` 的輔助函式，它將透過組件實例（透過 `ctx.$slot`）插入的插槽及其名稱作為參數．原始碼將被編譯為如下內容：\n\n```js\n_renderSlot(_ctx.$slots, \"my-slot\")\n```\n\n我們將在 AST 中將插槽定義表示為名為 `SlotOutletNode` 的節點．\\\n將以下定義新增到 `ast.ts`．\n\n```ts\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  SLOT, // [!code ++]\n}\n\n// ...\n\nexport type ElementNode = \n  | PlainElementNode \n  | ComponentNode \n  | SlotOutletNode // [!code ++]\n\n// ...\n\nexport interface SlotOutletNode extends BaseElementNode { // [!code ++]\n  tagType: ElementTypes.SLOT // [!code ++]\n  codegenNode: RenderSlotCall | undefined // [!code ++]\n} // [!code ++]\n\nexport interface RenderSlotCall extends CallExpression { // [!code ++]\n  callee: typeof RENDER_SLOT // [!code ++]\n  // $slots, name // [!code ++]\n  arguments: [string, string | ExpressionNode] // [!code ++]\n} // [!code ++]\n```\n\n讓我們編寫解析過程來產生這個 AST．\n\n在 `parse.ts` 中，任務很簡單：在解析標籤時，如果是 `\"slot\"`，將其更改為 `ElementTypes.SLOT`．\n\n```ts\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // ...\n  let tagType = ElementTypes.ELEMENT\n  if (tag === 'slot') { // [!code ++]\n    tagType = ElementTypes.SLOT // [!code ++]\n  } else if (isComponent(tag, context)) {\n    tagType = ElementTypes.COMPONENT\n  }\n}\n```\n\n現在我們已經到了這一點，下一步是實現轉換器來產生 `codegenNode`．\\\n我們需要為輔助函式建立一個 `JS_CALL_EXPRESSION`．\n\n作為預備步驟，將 `RENDER_SLOT` 新增到 `runtimeHelper.ts`．\n\n```ts\n// ...\nexport const RENDER_LIST = Symbol()\nexport const RENDER_SLOT = Symbol() // [!code ++]\nexport const MERGE_PROPS = Symbol()\n// ...\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: 'renderSlot', // [!code ++]\n  [MERGE_PROPS]: 'mergeProps',\n  // ...\n}\n```\n\n我們將實現一個名為 `transformSlotOutlet` 的新轉換器．\\\n任務非常簡單：當遇到 `ElementType.SLOT` 時，我們在 `node.props` 中搜尋 `name` 並為 `RENDER_SLOT` 產生一個 `JS_CALL_EXPRESSION`．\\\n我們還考慮名稱被繫結的情況，例如 `:name=\"slotName\"`．\n\n由於它很直接，這裡是完整的轉換器程式碼（請通讀）．\n\n```ts\nimport { camelize } from '../../shared'\nimport {\n  type CallExpression,\n  type ExpressionNode,\n  NodeTypes,\n  type SlotOutletNode,\n  createCallExpression,\n} from '../ast'\nimport { RENDER_SLOT } from '../runtimeHelpers'\nimport type { NodeTransform, TransformContext } from '../transform'\nimport { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'\n\nexport const transformSlotOutlet: NodeTransform = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { loc } = node\n    const { slotName } = processSlotOutlet(node, context)\n    const slotArgs: CallExpression['arguments'] = [\n      context.isBrowser ? `$slots` : `_ctx.$slots`,\n      slotName,\n    ]\n\n    node.codegenNode = createCallExpression(\n      context.helper(RENDER_SLOT),\n      slotArgs,\n      loc,\n    )\n  }\n}\n\ninterface SlotOutletProcessResult {\n  slotName: string | ExpressionNode\n}\n\nfunction processSlotOutlet(\n  node: SlotOutletNode,\n  context: TransformContext,\n): SlotOutletProcessResult {\n  let slotName: string | ExpressionNode = `\"default\"`\n\n  const nonNameProps = []\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (p.value) {\n        if (p.name === 'name') {\n          slotName = JSON.stringify(p.value.content)\n        } else {\n          p.name = camelize(p.name)\n          nonNameProps.push(p)\n        }\n      }\n    } else {\n      if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {\n        if (p.exp) slotName = p.exp\n      } else {\n        if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content)\n        }\n        nonNameProps.push(p)\n      }\n    }\n  }\n\n  return { slotName }\n}\n```\n\n將來，我們還將在這裡新增作用域插槽的屬性探索．\n\n需要注意的一點是 `<slot />` 元素也會被 `transformElement` 捕獲，所以我們將新增一個實現，在遇到 `ElementTypes.SLOT` 時跳過它．\n\n這是 `transformElement.ts`．\n\n```ts\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!\n\n    if ( // [!code ++]\n      !( // [!code ++]\n        node.type === NodeTypes.ELEMENT && // [!code ++]\n        (node.tagType === ElementTypes.ELEMENT || // [!code ++]\n          node.tagType === ElementTypes.COMPONENT) // [!code ++]\n      ) // [!code ++]\n    ) { // [!code ++]\n      return // [!code ++]\n    } // [!code ++]\n\n    // ...\n  }\n}\n```\n最後，透過在 `compile.ts` 中註冊 `transformSlotOutlet`，應該可以進行編譯．\n\n```ts\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformIf,\n      transformFor,\n      transformExpression,\n      transformSlotOutlet, // [!code ++]\n      transformElement,\n    ],\n    { bind: transformBind, on: transformOn },\n  ]\n}\n```\n\n我們還沒有實現執行時函式 `renderSlot`，所以我們將最後做這件事來完成插槽定義的實現．\n\n讓我們實現 `packages/runtime-core/helpers/renderSlot.ts`．\n\n```ts\nimport { Fragment, type VNode, createVNode } from '../vnode'\nimport type { Slots } from '../componentSlots'\n\nexport function renderSlot(slots: Slots, name: string): VNode {\n  let slot = slots[name]\n  if (!slot) {\n    slot = () => []\n  }\n\n  return createVNode(Fragment, {}, slot())\n}\n```\n\n插槽定義的實現現在已完成．\\\n接下來，讓我們實現插槽插入端的編譯器！\n\n到此為止的原始碼：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/080_component_slot_outlet)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/080-slot.md",
    "content": "---\nwip: true\n---\n\n# 插槽\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/085-component-slot-insert.md",
    "content": "# 支援插槽（使用）\n\n## 插槽插入\n\n接下來是插槽插入端的實現．\\\n這是在父組件端表示為 `<template #slot-name>` 的部分的編譯．\n\n正如開頭所解釋的，插槽被編譯為如下程式碼．\n\n```js\nh(Comp, null, {\n  default: _withCtx(() => [\n    h('button', { onClick: () => count.value++ }, `count is: ${count.value}`),\n  ]),\n})\n```\n\n也就是說，組件的子元素被作為 `SlotsExpression`（ObjectExpression）處理，每個插槽作為 `FunctionExpression` 產生，並用 `withCtx` 包裝．\n\n## withCtx 的作用\n\n`withCtx` 是一個輔助函式，用於在正確的組件實例上下文中執行插槽函式．這確保了插槽內的響應式依賴被追蹤到正確的組件．\n\n```ts\nexport function withCtx(\n  fn: Function,\n  ctx: ComponentInternalInstance | null = currentRenderingInstance,\n) {\n  if (!ctx) return fn;\n\n  const renderFnWithContext = (...args: any[]) => {\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    try {\n      return fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n    }\n  };\n\n  return renderFnWithContext;\n}\n```\n\n## 更新 AST\n\n首先，讓我們更新 AST 定義．\\\n新增一個名為 `SlotsExpression` 的類型，並在 `FunctionExpression` 中新增一個 `isSlot` 標誌來表示它是一個插槽函式．\n\n```ts\n// SlotsExpression is an ObjectExpression that represents the slots object\n// passed to a component. e.g., { default: () => [...], header: () => [...] }\nexport interface SlotsExpression extends ObjectExpression {}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode\n  newline: boolean\n  isSlot?: boolean // [!code ++]\n}\n```\n\n此外，將 `SlotsExpression` 新增到 `VNodeCall` 的 `children` 類型中．\n\n```ts\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL\n  tag: string | symbol\n  props: PropsExpression | undefined\n  children:\n    | TemplateChildNode[]\n    | TemplateTextChildNode\n    | ForRenderListExpression\n    | SlotsExpression // [!code ++]\n    | undefined\n}\n```\n\n## 新增輔助函式\n\n在 `runtimeHelpers.ts` 中新增 `WITH_CTX`．\n\n```ts\nexport const WITH_CTX = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_CTX]: 'withCtx',\n}\n```\n\n## 新增工具函式\n\n在 `utils.ts` 中新增 `findDir` 和 `isTemplateNode` 工具函式．\n\n```ts\nexport function isTemplateNode(\n  node: RootNode | TemplateChildNode,\n): node is PlainElementNode & { tag: 'template' } {\n  return (\n    node.type === NodeTypes.ELEMENT &&\n    node.tagType === ElementTypes.ELEMENT &&\n    node.tag === 'template'\n  )\n}\n\nexport function findDir(\n  node: ElementNode,\n  name: string | RegExp,\n  allowEmpty: boolean = false,\n): DirectiveNode | undefined {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i]\n    if (\n      p.type === NodeTypes.DIRECTIVE &&\n      (allowEmpty || p.exp) &&\n      (typeof name === 'string' ? p.name === name : name.test(p.name))\n    ) {\n      return p\n    }\n  }\n}\n```\n\n`isTemplateNode` 判斷是否是 `<template>` 標籤，`findDir` 查詢指定名稱的指令．\n\n## 實現 buildSlots\n\n在 `transforms/vSlot.ts` 中實現處理插槽插入的 `buildSlots` 函式．\n\n```ts\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type Property,\n  type SlotsExpression,\n  type TemplateChildNode,\n  createCallExpression,\n  createFunctionExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from '../ast'\nimport { WITH_CTX } from '../runtimeHelpers'\nimport type { TransformContext } from '../transform'\nimport { findDir, isStaticExp, isTemplateNode } from '../utils'\n\n// Build slots object for a component\nexport function buildSlots(\n  node: ElementNode,\n  context: TransformContext,\n): {\n  slots: SlotsExpression\n} {\n  const { children } = node\n  const slotsProperties: Property[] = []\n\n  // 1. Check for slot with slotProps on component itself.\n  //    <Comp v-slot=\"{ prop }\"/>\n  const onComponentSlot = findDir(node, 'slot', true)\n  if (onComponentSlot) {\n    const { arg, exp } = onComponentSlot\n    slotsProperties.push(\n      createObjectProperty(\n        arg || createSimpleExpression('default', true),\n        buildSlotFn(exp, children, node.loc, context),\n      ),\n    )\n  }\n\n  // 2. Iterate through children and check for template slots\n  //    <template v-slot:foo=\"{ prop }\">\n  let hasTemplateSlots = false\n  const implicitDefaultChildren: TemplateChildNode[] = []\n\n  for (let i = 0; i < children.length; i++) {\n    const slotElement = children[i]\n    let slotDir: DirectiveNode | undefined\n\n    if (\n      !isTemplateNode(slotElement) ||\n      !(slotDir = findDir(slotElement, 'slot', true))\n    ) {\n      // not a <template v-slot>, skip.\n      if (slotElement.type !== NodeTypes.COMMENT) {\n        implicitDefaultChildren.push(slotElement)\n      }\n      continue\n    }\n\n    hasTemplateSlots = true\n    const { children: slotChildren, loc: slotLoc } = slotElement\n    const {\n      arg: slotName = createSimpleExpression(`default`, true),\n      exp: slotProps,\n    } = slotDir\n\n    const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc, context)\n    slotsProperties.push(createObjectProperty(slotName, slotFunction))\n  }\n\n  if (!onComponentSlot) {\n    if (!hasTemplateSlots) {\n      // implicit default slot (on component)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, children, node.loc, context),\n        ),\n      )\n    } else if (implicitDefaultChildren.length) {\n      // implicit default slot (mixed with named slots)\n      slotsProperties.push(\n        createObjectProperty(\n          `default`,\n          buildSlotFn(undefined, implicitDefaultChildren, node.loc, context),\n        ),\n      )\n    }\n  }\n\n  const slots = createObjectExpression(\n    slotsProperties,\n    node.loc,\n  ) as SlotsExpression\n\n  return {\n    slots,\n  }\n}\n\nfunction buildSlotFn(\n  props: ExpressionNode | undefined,\n  children: TemplateChildNode[],\n  loc: any,\n  context: TransformContext,\n) {\n  const fn = createFunctionExpression(\n    props,\n    children,\n    false /* newline */,\n    children.length ? children[0].loc : loc,\n  )\n  fn.isSlot = true\n  return createCallExpression(context.helper(WITH_CTX), [fn], loc)\n}\n```\n\n`buildSlots` 函式處理以下三種模式：\n\n1. **組件本身有 v-slot 的情況**（`<Comp v-slot=\"{ prop }\"/>`）\n2. **使用 template 標籤定義具名插槽的情況**（`<template #foo>`）\n3. **隱式預設插槽**（沒有具名插槽時的子元素）\n\n## 更新 transformElement\n\n最後，更新 `transformElement.ts`，使用 `buildSlots` 處理組件的子元素．\n\n```ts\nimport { buildSlots } from './vSlot'\n\n// ...\n\n// children\nif (node.children.length > 0) {\n  if (isComponent) {\n    // For components, build slots object // [!code ++]\n    const { slots } = buildSlots(node, context) // [!code ++]\n    vnodeChildren = slots as SlotsExpression // [!code ++]\n  } else if (node.children.length === 1) {\n    const child = node.children[0]\n    const type = child.type\n    const hasDynamicTextChild = type === NodeTypes.INTERPOLATION\n\n    if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n      vnodeChildren = child as TemplateTextChildNode\n    } else {\n      vnodeChildren = node.children\n    }\n  } else {\n    vnodeChildren = node.children\n  }\n}\n```\n\n這樣，插槽插入端的編譯就完成了．\\\n組件的子元素會自動轉換為插槽物件，產生如下程式碼．\n\n```vue\n<Comp>\n  <template #header>\n    <h1>Header</h1>\n  </template>\n  <template #default>\n    <p>Content</p>\n  </template>\n</Comp>\n```\n\n↓\n\n```js\n_createVNode(_component_Comp, null, {\n  header: _withCtx(() => [_createVNode('h1', null, 'Header')]),\n  default: _withCtx(() => [_createVNode('p', null, 'Content')]),\n})\n```\n\n基本的插槽編譯器實現現在已完成！\n\n到此為止的原始碼：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/085_component_slot_insert)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/090-other-directives.md",
    "content": "# 其他指令\n\n到目前為止，我們已經實現了 v-bind，v-on，v-if，v-for，v-model 等主要指令．\\\n在本章中，我們將實現其餘的內建指令．\n\n我們要實現的指令如下：\n\n- v-text\n- v-html\n- v-cloak\n- v-pre\n\n關於 v-show，由於它需要執行時指令機制，我們將在自訂指令章節中介紹．\\\n另外，v-once 和 v-memo 與最佳化相關，計劃在 Web Application Essentials 的 Optimizations 章節中介紹．\n\n## v-text\n\n### 目標開發者介面\n\nv-text 是一個更新元素 textContent 的指令．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello!')\n    return { msg }\n  },\n}\n</script>\n\n<template>\n  <span v-text=\"msg\"></span>\n  <!-- 等同於下面的寫法 -->\n  <span>{{ msg }}</span>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-text\n\n### 實現方針\n\nv-text 的實現非常簡單．\\\n在編譯時，只需將 v-text 指令轉換為 `textContent` 屬性的綁定即可．\n\n```html\n<span v-text=\"msg\"></span>\n```\n\n↓\n\n```ts\nh('span', { textContent: msg })\n```\n\n### 在 compiler-dom 中實現 transformer\n\n由於 v-text 是 DOM 特有的指令，我們在 compiler-dom 中實現它．\n\n建立 `packages/compiler-dom/src/transforms/vText.ts`．\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-text is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-text will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\n關鍵點如下：\n\n- 如果 exp 不存在則輸出錯誤\n- 如果存在子元素則輸出警告並清除子元素（因為 v-text 會覆蓋子元素）\n- 將 exp 綁定為 `textContent` 屬性\n\n然後在 `packages/compiler-dom/src/index.ts` 中註冊 transformer．\n\n```ts\nimport { transformVText } from './transforms/vText'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText, // [!code ++]\n}\n```\n\n這樣 v-text 的實現就完成了！\n\n## v-html\n\n### 目標開發者介面\n\nv-html 是一個更新元素 innerHTML 的指令．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const rawHtml = ref('<span style=\"color: red\">This should be red.</span>')\n    return { rawHtml }\n  },\n}\n</script>\n\n<template>\n  <p>Using v-html directive: <span v-html=\"rawHtml\"></span></p>\n</template>\n```\n\nhttps://vuejs.org/api/built-in-directives.html#v-html\n\n::: warning\n由於 v-html 直接操作 innerHTML，可能成為 XSS 漏洞的來源．\\\n請避免使用 v-html 顯示不受信任的使用者輸入．\n:::\n\n### 實現方針\n\n與 v-text 類似，v-html 在編譯時轉換為 `innerHTML` 屬性的綁定．\n\n```html\n<span v-html=\"rawHtml\"></span>\n```\n\n↓\n\n```ts\nh('span', { innerHTML: rawHtml })\n```\n\n### 在 compiler-dom 中實現 transformer\n\n建立 `packages/compiler-dom/src/transforms/vHtml.ts`．\n\n```ts\nimport {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from '@chibivue/compiler-core'\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir\n  if (!exp) {\n    console.error(\n      `v-html is missing expression.`,\n    )\n  }\n  if (node.children.length) {\n    console.error(\n      `v-html will override element children.`,\n    )\n    node.children.length = 0\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression('', true),\n      ),\n    ],\n  }\n}\n```\n\n結構與 v-text 幾乎相同．唯一的區別是使用 `innerHTML` 而不是 `textContent`．\n\n在 `packages/compiler-dom/src/index.ts` 中註冊 transformer．\n\n```ts\nimport { transformVHtml } from './transforms/vHtml'\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel,\n  text: transformVText,\n  html: transformVHtml, // [!code ++]\n}\n```\n\n這樣 v-html 的實現也完成了！\n\n## v-cloak\n\n### 目標開發者介面\n\nv-cloak 是一個用於在元件掛載前隱藏元素的指令．\\\n它與 CSS 配合使用，防止使用者看到未編譯的模板語法（如 mustache）．\n\n```css\n[v-cloak] {\n  display: none;\n}\n```\n\n```text\n<div v-cloak>\n  ｛｛ message ｝｝\n</div>\n```\n\n掛載後，v-cloak 屬性會自動移除．\n\nhttps://vuejs.org/api/built-in-directives.html#v-cloak\n\n### 實現方針\n\nv-cloak 的實現非常簡單．\\\n只需在掛載時從元素中移除 v-cloak 屬性即可．\n\n這是在執行時而不是編譯器中處理的．\\\n具體來說，我們在 `renderer.ts` 的 `mountElement` 函式中新增處理．\n\n### 在執行時實現\n\n在 `packages/runtime-core/src/renderer.ts` 的 `mountElement` 函式中新增以下處理．\n\n```ts\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null,\n) => {\n  let el: RendererElement\n  const { type, props, children, shapeFlag } = vnode\n\n  el = vnode.el = hostCreateElement(type as string)\n\n  // ... 現有處理 ...\n\n  // 移除 v-cloak // [!code ++]\n  if (props && 'v-cloak' in props) { // [!code ++]\n    delete (el as any)['v-cloak'] // [!code ++]\n    hostRemoveAttribute(el, 'v-cloak') // [!code ++]\n  } // [!code ++]\n\n  hostInsert(el, container, anchor)\n\n  // ... 現有處理 ...\n}\n```\n\n雖然可以使用現有的 `hostPatchProp` 來實現 `hostRemoveAttribute`，但讓我們簡單地將其新增到 `nodeOps` 中．\n\n新增到 `packages/runtime-dom/src/nodeOps.ts`．\n\n```ts\nexport const nodeOps: Omit<RendererOptions, 'patchProp'> = {\n  // ... 現有處理 ...\n  removeAttribute: (el, key) => {\n    el.removeAttribute(key)\n  },\n}\n```\n\n還需要新增到 `packages/runtime-core/src/renderer.ts` 的 `RendererOptions` 型別中．\n\n```ts\nexport interface RendererOptions<\n  HostNode = RendererNode,\n  HostElement = RendererElement,\n> {\n  // ... 現有處理 ...\n  removeAttribute(el: HostElement, key: string): void\n}\n```\n\n這樣 v-cloak 的實現就完成了！\n\n## v-pre\n\n### 目標開發者介面\n\nv-pre 是一個跳過該元素及其所有子元素編譯的指令．\\\n當你想要原樣顯示 mustache 語法時使用．\n\n```text\n<template>\n  <span v-pre>｛｛ this will not be compiled ｝｝</span>\n</template>\n```\n\n上面的模板將原樣顯示文字 `｛｛ this will not be compiled ｝｝`．\n\nhttps://vuejs.org/api/built-in-directives.html#v-pre\n\n### 實現方針\n\n與其他指令不同，v-pre 在解析器階段處理．\\\n當偵測到帶有 v-pre 屬性的元素時，跳過該元素及其子元素的指令和 mustache 語法解析．\n\n### 在解析器中實現\n\n在 `packages/compiler-core/src/parse.ts` 中新增 v-pre 處理．\n\n首先，在解析器上下文中新增 `inVPre` 標誌．\n\n```ts\nexport interface ParserContext {\n  // ... 現有屬性 ...\n  inVPre: boolean // [!code ++]\n}\n\nfunction createParserContext(content: string, options: ParserOptions): ParserContext {\n  return {\n    // ... 現有處理 ...\n    inVPre: false, // [!code ++]\n  }\n}\n```\n\n接下來，在解析元素時檢查 v-pre 屬性，如果存在則將 `inVPre` 設定為 true．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // Start tag\n  const element = parseTag(context, TagType.Start)\n\n  // 檢查 v-pre // [!code ++]\n  const isPreBoundary = element.props.some( // [!code ++]\n    p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre' // [!code ++]\n  ) // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = true // [!code ++]\n  } // [!code ++]\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = children\n\n    // End tag\n    if (startsWithEndTagOpen(context.source, element.tag)) {\n      parseTag(context, TagType.End)\n    }\n  }\n\n  // v-pre 結束 // [!code ++]\n  if (isPreBoundary) { // [!code ++]\n    context.inVPre = false // [!code ++]\n  } // [!code ++]\n\n  return element\n}\n```\n\n然後，在 `inVPre` 為 true 時跳過指令和 mustache 語法的解析．\n\n修改 `parseAttribute` 函式．\n\n```ts\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // ... 屬性名稱解析 ...\n\n  // 在 v-pre 中不作為指令解析 // [!code ++]\n  if (context.inVPre) { // [!code ++]\n    return { // [!code ++]\n      type: NodeTypes.ATTRIBUTE, // [!code ++]\n      name, // [!code ++]\n      value: value && { // [!code ++]\n        type: NodeTypes.TEXT, // [!code ++]\n        content: value.content, // [!code ++]\n        loc: value.loc, // [!code ++]\n      }, // [!code ++]\n      loc, // [!code ++]\n    } // [!code ++]\n  } // [!code ++]\n\n  // 指令解析 ...\n}\n```\n\n同樣修改 `parseChildren` 函式以跳過 mustache 語法解析．\n\n```ts\nfunction parseChildren(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context, ancestors)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined = undefined\n\n    if (startsWith(s, context.options.delimiters[0])) {\n      // 在 v-pre 中跳過 mustache // [!code ++]\n      if (!context.inVPre) { // [!code ++]\n        node = parseInterpolation(context)\n      } // [!code ++]\n    } else if (s[0] === '<') {\n      // ... 元素解析 ...\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\n這樣 v-pre 的實現就完成了！\n\n## 驗證行為\n\n讓我們驗證實現的指令是否正常運作．\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const msg = ref('Hello, chibivue!')\n    const rawHtml = ref('<span style=\"color: red\">Red text</span>')\n    return { msg, rawHtml }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <h2>v-text</h2>\n    <span v-text=\"msg\"></span>\n\n    <h2>v-html</h2>\n    <div v-html=\"rawHtml\"></div>\n\n    <h2>v-pre</h2>\n    <span v-pre>｛｛ msg ｝｝ will not be compiled</span>\n  </div>\n</template>\n```\n\n運作正常嗎？\\\n這樣基本的內建指令實現就完成了！\n\nv-show 和自訂指令將在下一章介紹．\\\nv-once 和 v-memo 計劃在最佳化章節中介紹．\n\n到此為止的原始碼：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/090_other_directives)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/100-chore-compiler.md",
    "content": "# 編譯器細節優化\n\n在本章中，我們將對模板編譯器進行一些調整以提高其品質．\\\n主要涉及以下兩個主題：\n\n1. **空白處理** - 刪除和壓縮不必要的空白\n2. **文字節點合併** - 高效合併相鄰的文字節點\n\n這些是為了提高生成程式碼品質的優化，而不是可見的功能．\n\n## 空白處理\n\n### 問題\n\n在當前的實現中，模板中的所有空白都會被原樣保留．\\\n考慮以下模板：\n\n```html\n<div>\n  <span>Hello</span>\n  <span>World</span>\n</div>\n```\n\n在當前實現中，`<div>` 和 `<span>` 之間的換行和縮排會作為文字節點被保留．\\\n這會生成不必要的節點，可能影響效能．\n\n### Vue.js 的方法\n\nVue.js 使用 `whitespace` 選項來控制空白的處理方式．\n\n```ts\ntype WhitespaceStrategy = 'preserve' | 'condense'\n```\n\n- **`'condense'`**（預設）：壓縮連續的空白並刪除不必要的空白\n- **`'preserve'`**：原樣保留空白\n\n### condense 模式的行為\n\n在 condense 模式下，空白按照以下規則處理：\n\n1. **開頭/結尾的純空白文字節點** → 刪除\n2. **包含換行的元素間空白** → 刪除\n3. **連續的空白** → 壓縮為單個空格\n4. **不包含換行的元素間空白** → 保留（壓縮為單個空格）\n\n範例：\n\n```html\n<div>   <span/>    </div>\n<!-- 結果：只有 <span/> 作為子節點（周圍的空格被刪除） -->\n\n<div/>\n<div/>\n<div/>\n<!-- 結果：只有 3 個 div 元素（包含換行的空白被刪除） -->\n\n<span>foo</span>  <span>bar</span>\n<!-- 結果：元素間的空格被保留（沒有換行） -->\n```\n\n### 實現\n\n首先，在 `ParserOptions` 中添加 `whitespace` 選項．\n\n`packages/compiler-core/src/options.ts`：\n\n```ts\nexport interface ParserOptions {\n  // ... 現有選項 ...\n  whitespace?: 'preserve' | 'condense' // [!code ++]\n}\n```\n\n在 `packages/compiler-core/src/parse.ts` 中添加空白處理函數．\n\n```ts\nfunction isAllWhitespace(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (\n      c !== 0x20 && // 空格\n      c !== 0x09 && // 製表符\n      c !== 0x0a && // 換行\n      c !== 0x0c && // 換頁\n      c !== 0x0d    // 回車\n    ) {\n      return false\n    }\n  }\n  return true\n}\n\nfunction hasNewlineChar(content: string): boolean {\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    if (c === 0x0a || c === 0x0d) {\n      return true\n    }\n  }\n  return false\n}\n\nfunction condense(content: string): string {\n  let result = ''\n  let prevIsWhitespace = false\n  for (let i = 0; i < content.length; i++) {\n    const c = content.charCodeAt(i)\n    const isWhitespace =\n      c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d\n    if (isWhitespace) {\n      if (!prevIsWhitespace) {\n        result += ' '\n        prevIsWhitespace = true\n      }\n    } else {\n      result += content[i]\n      prevIsWhitespace = false\n    }\n  }\n  return result\n}\n\nfunction condenseWhitespace(\n  nodes: TemplateChildNode[],\n  context: ParserContext,\n): TemplateChildNode[] {\n  const shouldCondense = context.options.whitespace !== 'preserve'\n  let removedWhitespace = false\n\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i]\n    if (node.type === NodeTypes.TEXT) {\n      if (!context.inPre) {\n        if (isAllWhitespace(node.content)) {\n          const prev = nodes[i - 1]?.type\n          const next = nodes[i + 1]?.type\n          // 以下情況刪除：\n          // - 開頭或結尾的空白\n          // - (condense 模式) 註釋之間的空白\n          // - (condense 模式) 註釋和元素之間的空白\n          // - (condense 模式) 包含換行的元素間空白\n          if (\n            !prev ||\n            !next ||\n            (shouldCondense &&\n              ((prev === NodeTypes.COMMENT &&\n                (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||\n                (prev === NodeTypes.ELEMENT &&\n                  (next === NodeTypes.COMMENT ||\n                    (next === NodeTypes.ELEMENT &&\n                      hasNewlineChar(node.content))))))\n          ) {\n            removedWhitespace = true\n            nodes[i] = null as any\n          } else {\n            // 否則壓縮為單個空格\n            node.content = ' '\n          }\n        } else if (shouldCondense) {\n          // condense 模式下壓縮連續空白\n          node.content = condense(node.content)\n        }\n      }\n    }\n  }\n\n  return removedWhitespace ? nodes.filter(Boolean) : nodes\n}\n```\n\n然後在解析元素時呼叫此函數．\n\n```ts\nfunction parseElement(\n  context: ParserContext,\n  ancestors: ElementNode[],\n): ElementNode | undefined {\n  // ... 現有程式碼 ...\n\n  // Children\n  if (!element.isSelfClosing) {\n    ancestors.push(element)\n    const children = parseChildren(context, ancestors)\n    ancestors.pop()\n    element.children = condenseWhitespace(children, context) // [!code ++]\n    // element.children = children // [!code --]\n\n    // ...\n  }\n\n  return element\n}\n```\n\n同樣對根節點應用相同的處理．\n\n```ts\nexport const baseParse = (\n  content: string,\n  options: ParserOptions = {},\n): RootNode => {\n  const context = createParserContext(content, options)\n  const children = parseChildren(context, [])\n  return createRoot(condenseWhitespace(children, context)) // [!code ++]\n  // return createRoot(children) // [!code --]\n}\n```\n\n## 文字節點合併 (transformText)\n\n### 問題\n\n在當前實現中，文字節點和 mustache 語法（`{{ }}`）被作為單獨的節點處理．\n\n```html\n<div>abc {{ d }} {{ e }}</div>\n```\n\n這個模板有以下子節點：\n- `TEXT`: \"abc \"\n- `INTERPOLATION`: d\n- `TEXT`: \" \"\n- `INTERPOLATION`: e\n\n在程式碼生成時單獨處理這些節點效率不高．\n\n### Vue.js 的方法\n\nVue.js 使用名為 `transformText` 的轉換器將相鄰的文字節點和 mustache 語法合併為一個 `CompoundExpression`．\n\n合併後：\n```ts\n// \"abc \" + d + \" \" + e\ncreateCompoundExpression(['abc ', d, ' ', e])\n```\n\n這允許在程式碼生成時輸出高效的連接操作．\n\n### 實現\n\n建立 `packages/compiler-core/src/transforms/transformText.ts`．\n\n```ts\nimport type { NodeTransform } from '../transform'\nimport {\n  type CompoundExpressionNode,\n  ElementTypes,\n  NodeTypes,\n  createCallExpression,\n  createCompoundExpression,\n} from '../ast'\nimport { isText } from '../utils'\nimport { CREATE_TEXT } from '../runtimeHelpers'\nimport { PatchFlags } from '@chibivue/shared'\n\n// 將相鄰的文字節點和 mustache 合併為單個表達式\n// 例如：<div>abc {{ d }} {{ e }}</div> 應該只有一個子節點\nexport const transformText: NodeTransform = (node, context) => {\n  if (\n    node.type === NodeTypes.ROOT ||\n    node.type === NodeTypes.ELEMENT ||\n    node.type === NodeTypes.FOR ||\n    node.type === NodeTypes.IF_BRANCH\n  ) {\n    // 在子節點處理完成後執行\n    return () => {\n      const children = node.children\n      let currentContainer: CompoundExpressionNode | undefined = undefined\n      let hasText = false\n\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child)) {\n          hasText = true\n          for (let j = i + 1; j < children.length; j++) {\n            const next = children[j]\n            if (isText(next)) {\n              if (!currentContainer) {\n                currentContainer = children[i] = createCompoundExpression(\n                  [child],\n                  child.loc,\n                )\n              }\n              // 合併相鄰的文字節點\n              currentContainer.children.push(` + `, next)\n              children.splice(j, 1)\n              j--\n            } else {\n              currentContainer = undefined\n              break\n            }\n          }\n        }\n      }\n\n      if (\n        !hasText ||\n        // 對於只有單個文字子節點的普通元素，保持原樣\n        // 執行時有直接設定 textContent 的優化路徑\n        (children.length === 1 &&\n          (node.type === NodeTypes.ROOT ||\n            (node.type === NodeTypes.ELEMENT &&\n              node.tagType === ElementTypes.ELEMENT &&\n              !node.props.find(\n                p =>\n                  p.type === NodeTypes.DIRECTIVE &&\n                  !context.directiveTransforms[p.name],\n              ))))\n      ) {\n        return\n      }\n\n      // 將文字節點轉換為 createTextVNode(text) 呼叫\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i]\n        if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {\n          const callArgs: any[] = []\n          // createTextVNode 預設為單個空格，\n          // 所以單個空格時可以省略參數\n          if (child.type !== NodeTypes.TEXT || child.content !== ' ') {\n            callArgs.push(child)\n          }\n          // 為動態文字添加標誌以在區塊內進行補丁\n          if (!context.ssr && !isStaticNode(child)) {\n            callArgs.push(PatchFlags.TEXT)\n          }\n          children[i] = {\n            type: NodeTypes.TEXT_CALL,\n            content: child,\n            loc: child.loc,\n            codegenNode: createCallExpression(\n              context.helper(CREATE_TEXT),\n              callArgs,\n            ),\n          }\n        }\n      }\n    }\n  }\n}\n\nfunction isStaticNode(node: any): boolean {\n  if (node.type === NodeTypes.TEXT) {\n    return true\n  }\n  if (node.type === NodeTypes.INTERPOLATION) {\n    return node.content.isStatic\n  }\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    return node.children.every((child: any) => {\n      if (typeof child === 'string') return true\n      return isStaticNode(child)\n    })\n  }\n  return false\n}\n```\n\n在 `packages/compiler-core/src/utils.ts` 中添加 `isText` 輔助函數．\n\n```ts\nexport function isText(\n  node: TemplateChildNode,\n): node is TextNode | InterpolationNode {\n  return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION\n}\n```\n\n在 `packages/compiler-core/src/ast.ts` 中添加 `TEXT_CALL` 節點類型和 `createCallExpression`．\n\n```ts\nexport const enum NodeTypes {\n  // ... 現有類型 ...\n  TEXT_CALL, // [!code ++]\n}\n\nexport interface TextCallNode extends Node {\n  type: NodeTypes.TEXT_CALL\n  content: TextNode | InterpolationNode | CompoundExpressionNode\n  codegenNode: CallExpression\n}\n\nexport function createCallExpression(\n  callee: string,\n  args: CallExpression['arguments'] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  }\n}\n```\n\n在 `packages/compiler-core/src/runtimeHelpers.ts` 中添加 `CREATE_TEXT`．\n\n```ts\nexport const CREATE_TEXT = Symbol('createTextVNode')\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ... 現有助手 ...\n  [CREATE_TEXT]: 'createTextVNode',\n}\n```\n\n### 註冊轉換器\n\n在 `packages/compiler-core/src/compile.ts` 中註冊轉換器．\n\n```ts\nimport { transformText } from './transforms/transformText'\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [\n      transformElement,\n      transformSlotOutlet,\n      transformText, // [!code ++]\n    ],\n    {\n      on: transformOn,\n      bind: transformBind,\n      if: transformIf,\n      for: transformFor,\n      model: transformModel,\n    },\n  ]\n}\n```\n\n### 更新程式碼生成\n\n在 `packages/compiler-core/src/codegen.ts` 中添加 `TEXT_CALL` 節點處理．\n\n```ts\nfunction genNode(node: any, context: CodegenContext) {\n  switch (node.type) {\n    // ... 現有情況 ...\n    case NodeTypes.TEXT_CALL: // [!code ++]\n      genNode(node.codegenNode, context) // [!code ++]\n      break // [!code ++]\n  }\n}\n```\n\n### 更新執行時\n\n在 `packages/runtime-core/src/vnode.ts` 中添加 `createTextVNode`．\n\n```ts\nexport function createTextVNode(text: string = ' ', flag: number = 0): VNode {\n  return createVNode(Text, null, text, flag)\n}\n```\n\n從 `packages/runtime-core/src/index.ts` 導出．\n\n```ts\nexport { createTextVNode } from './vnode'\n```\n\n## 測試\n\n讓我們用以下模板進行驗證：\n\n```vue\n<script>\nimport { ref } from 'chibivue'\n\nexport default {\n  setup() {\n    const name = ref('World')\n    return { name }\n  },\n}\n</script>\n\n<template>\n  <div>\n    <p>Hello {{ name }}!</p>\n  </div>\n</template>\n```\n\n檢查編譯結果時，你應該看到：\n- 不必要的空白（換行和縮排）已被刪除\n- `Hello `，`{{ name }}` 和 `!` 已被合併\n\n編譯器的品質現在得到了提升！\n\n本章節的原始碼：\\\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/50_basic_template_compiler/100_chore_compiler)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/110-parser-optimization.md",
    "content": "# 解析器優化\n\n::: info 關於本章\n本章介紹 Vue 3.4 中引入的新解析器架構．\\\n基於 htmlparser2 的狀態機 tokenizer 使解析速度提高了 2 倍．\n:::\n\n## 背景\n\n在 Vue 3.4 中，模板編譯器的內部實現進行了重大重構．到目前為止，我們在 chibivue 中實現的解析器是基於 Vue 3.3 及更早版本的架構．\n\n### 傳統解析器（Vue 3.3 及更早版本）\n\n傳統的 Vue 解析器是**遞迴下降解析器（recursive descent parser）**：\n\n```ts\n// 傳統實現\nfunction parseChildren(context: ParserContext): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = []\n\n  while (!isEnd(context)) {\n    const s = context.source\n    let node: TemplateChildNode | undefined\n\n    if (startsWith(s, '{{')) {\n      node = parseInterpolation(context)\n    } else if (s[0] === '<') {\n      if (/[a-z]/i.test(s[1])) {\n        node = parseElement(context)\n      }\n    }\n\n    if (!node) {\n      node = parseText(context)\n    }\n\n    nodes.push(node)\n  }\n\n  return nodes\n}\n```\n\n這種方式的問題：\n- 大量使用**正規表示式**\n- 頻繁的**前瞻（look-ahead）搜尋**\n- 多次遍歷模板字串\n\n### 新解析器（Vue 3.4）\n\nVue 3.4 引入了基於 [htmlparser2](https://github.com/fb55/htmlparser2) 的**狀態機 tokenizer**：\n\n```ts\n// 新實現\nconst enum State {\n  Text,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  BeforeAttrName,\n  InAttrName,\n  // ...\n}\n\nclass Tokenizer {\n  private state = State.Text\n  private index = 0\n\n  parse(input: string) {\n    for (let i = 0; i < input.length; i++) {\n      this.index = i\n      this.consume(input.charCodeAt(i))\n    }\n  }\n\n  private consume(char: number) {\n    switch (this.state) {\n      case State.Text:\n        this.handleText(char)\n        break\n      case State.BeforeTagName:\n        this.handleBeforeTagName(char)\n        break\n      // ...\n    }\n  }\n}\n```\n\n這種方式的優點：\n- **單次遍歷**模板字串\n- 不使用正規表示式（或最小化使用）\n- **逐字元**處理效率高\n- 清晰的**狀態轉換**提高可維護性\n\n<KawaikoNote variant=\"surprise\" title=\"速度提升 2 倍！\">\n\n這個狀態機 tokenizer 實現了**一致的 2 倍**解析速度提升！\\\n令人驚訝的是，僅僅通過避免正規表示式和前瞻搜尋，一個字元一個字元地順序處理，就能實現如此顯著的效能提升．\n\n</KawaikoNote>\n\n## 狀態機 Tokenizer\n\n狀態機 tokenizer 根據當前狀態決定如何處理下一個字元．\n\n### 狀態定義\n\n```ts\nconst enum State {\n  // 文字\n  Text = 1,\n\n  // 插值（Mustache）\n  InterpolationOpen,     // 檢測 {{\n  Interpolation,         // {{ 內的內容\n  InterpolationClose,    // 檢測 }}\n\n  // 標籤\n  BeforeTagName,         // < 之後\n  InTagName,             // 標籤名內部\n  InSelfClosingTag,      // 檢測 />\n\n  // 屬性\n  BeforeAttrName,        // 屬性名之前\n  InAttrName,            // 屬性名內部\n  AfterAttrName,         // 屬性名之後（= 之前）\n  BeforeAttrValue,       // 屬性值之前\n  InAttrValueDq,         // 雙引號內的屬性值\n  InAttrValueSq,         // 單引號內的屬性值\n  InAttrValueNq,         // 無引號的屬性值\n\n  // 指令\n  InDirName,             // 指令名（v-xxx）\n  InDirArg,              // 指令參數（:xxx）\n  InDirDynamicArg,       // 動態參數（[xxx]）\n  InDirModifier,         // 修飾符（.xxx）\n}\n```\n\n### 狀態轉換範例\n\n```\n<div v-if=\"show\">Hello {{ name }}</div>\n```\n\n此範例的狀態轉換：\n\n```\n< → BeforeTagName\nd → InTagName\ni → InTagName\nv → InTagName\n(空格) → BeforeAttrName\nv → InAttrName (或 InDirName)\n- → InDirName\ni → InDirName\nf → InDirName\n= → BeforeAttrValue\n\" → InAttrValueDq\ns → InAttrValueDq\nh → InAttrValueDq\no → InAttrValueDq\nw → InAttrValueDq\n\" → BeforeAttrName\n> → Text\nH → Text\n...\n{ → InterpolationOpen\n{ → Interpolation\n(空格) → Interpolation\nn → Interpolation\na → Interpolation\nm → Interpolation\ne → Interpolation\n(空格) → Interpolation\n} → InterpolationClose\n} → Text\n...\n```\n\n## Visitor 模式\n\n新解析器使用 **Visitor 模式**將 tokenizer 與 AST 建構分離．\n\n### Callbacks 介面\n\n```ts\ninterface Callbacks {\n  onText(start: number, end: number): void\n  onInterpolation(start: number, end: number): void\n  onOpenTag(tag: string, start: number): void\n  onCloseTag(tag: string, start: number, end: number): void\n  onSelfClosingTag(tag: string, start: number, end: number): void\n  onAttr(name: string, value: string | undefined, start: number, end: number): void\n  onDirective(\n    name: string,\n    arg: string | undefined,\n    modifiers: string[],\n    value: string | undefined,\n    start: number,\n    end: number\n  ): void\n  onComment(start: number, end: number): void\n}\n```\n\n### Tokenizer 與 Parser 的分離\n\n```ts\nclass Tokenizer {\n  private cbs: Callbacks\n\n  constructor(callbacks: Callbacks) {\n    this.cbs = callbacks\n  }\n\n  // Tokenizer 發出事件\n  private emitOpenTag(tag: string, start: number) {\n    this.cbs.onOpenTag(tag, start)\n  }\n\n  private emitText(start: number, end: number) {\n    this.cbs.onText(start, end)\n  }\n}\n\n// Parser 實現 Callbacks 來建構 AST\nclass Parser implements Callbacks {\n  private stack: ElementNode[] = []\n  private root: RootNode\n\n  onOpenTag(tag: string, start: number) {\n    const element: ElementNode = {\n      type: NodeTypes.ELEMENT,\n      tag,\n      children: [],\n      // ...\n    }\n    this.stack.push(element)\n  }\n\n  onCloseTag(tag: string, start: number, end: number) {\n    const element = this.stack.pop()!\n    const parent = this.stack[this.stack.length - 1]\n    if (parent) {\n      parent.children.push(element)\n    } else {\n      this.root.children.push(element)\n    }\n  }\n\n  onText(start: number, end: number) {\n    const parent = this.stack[this.stack.length - 1]\n    const text: TextNode = {\n      type: NodeTypes.TEXT,\n      content: this.source.slice(start, end),\n      // ...\n    }\n    parent.children.push(text)\n  }\n}\n```\n\n### 優點\n\n1. **關注點分離**：Tokenizer 只專注於字元解析，Parser 只專注於 AST 建構\n2. **可測試性**：每個元件可以獨立測試\n3. **可複用性**：Tokenizer 可以重用於其他目的（語法高亮，Lint 等）\n4. **效能**：不生成不必要的中間資料結構\n\n<KawaikoNote variant=\"question\" title=\"什麼是 Visitor 模式？\">\n\nVisitor 模式是一種「將資料結構與其處理分離」的設計模式．\\\nTokenizer「只讀取模板並發出事件」，Parser「只接收事件並建構 AST」——簡單的職責劃分．\\\n這使得程式碼更容易理解和測試！\n\n</KawaikoNote>\n\n## 效能比較\n\n根據 Vue 3.4 部落格文章：\n\n| 模板大小 | 改進率 |\n|---------|-------|\n| 小型 | ~2x |\n| 中型 | ~2x |\n| 大型 | ~2x |\n\n實現了一致的 2 倍加速．\n\n此改進惠及整個生態系統：\n- **Volar**：IDE 補全和型別檢查\n- **vue-tsc**：型別檢查\n- **建構工具**：Vite，Webpack 等\n- **社群外掛**：ESLint，Prettier 等\n\n## chibivue 中的實現\n\n::: warning\n當前 chibivue 使用傳統的遞迴下降解析器．\\\n遷移到 Vue 3.4 風格的 tokenizer 正在考慮作為未來的工作．\n:::\n\n基本實現概要：\n\n<KawaikoNote variant=\"base\" title=\"有興趣就來挑戰！\">\n\n本章介紹的狀態機 tokenizer 在 chibivue 中還沒有實現，但如果你有興趣，可以嘗試自己實現！\\\n參考 Vue 3.4 的原始碼和 htmlparser2 會加深你的理解．\\\n解析器優化是框架開發中非常重要的技能．\n\n</KawaikoNote>\n\n```ts\n// packages/compiler-core/tokenizer.ts\nconst enum State {\n  Text = 1,\n  InterpolationOpen,\n  Interpolation,\n  InterpolationClose,\n  BeforeTagName,\n  InTagName,\n  // ...\n}\n\nconst enum CharCodes {\n  Lt = 0x3c,      // <\n  Gt = 0x3e,      // >\n  Slash = 0x2f,   // /\n  Eq = 0x3d,      // =\n  OpenBrace = 0x7b,  // {\n  CloseBrace = 0x7d, // }\n  // ...\n}\n\nexport class Tokenizer {\n  private state = State.Text\n  private buffer = ''\n  private sectionStart = 0\n  private index = 0\n\n  constructor(private cbs: Callbacks) {}\n\n  parse(input: string) {\n    this.buffer = input\n    while (this.index < input.length) {\n      const c = input.charCodeAt(this.index)\n      switch (this.state) {\n        case State.Text:\n          this.stateText(c)\n          break\n        case State.InterpolationOpen:\n          this.stateInterpolationOpen(c)\n          break\n        // ...\n      }\n      this.index++\n    }\n    this.finish()\n  }\n\n  private stateText(c: number) {\n    if (c === CharCodes.Lt) {\n      if (this.index > this.sectionStart) {\n        this.cbs.onText(this.sectionStart, this.index)\n      }\n      this.state = State.BeforeTagName\n      this.sectionStart = this.index\n    } else if (c === CharCodes.OpenBrace) {\n      this.state = State.InterpolationOpen\n    }\n  }\n\n  private stateInterpolationOpen(c: number) {\n    if (c === CharCodes.OpenBrace) {\n      if (this.index > this.sectionStart + 1) {\n        this.cbs.onText(this.sectionStart, this.index - 1)\n      }\n      this.state = State.Interpolation\n      this.sectionStart = this.index + 1\n    } else {\n      this.state = State.Text\n    }\n  }\n\n  // ...\n}\n```\n\n## 總結\n\n- Vue 3.4 引入了基於 htmlparser2 的狀態機 tokenizer\n- 透過只掃描模板字串一次，解析速度提高了 2 倍\n- Visitor 模式分離了 tokenizer 和 AST 建構，提高了可維護性\n- 此優化惠及整個生態系統（Volar，vue-tsc 等）\n\n## 參考連結\n\n- [Announcing Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) - Vue 官方部落格\n- [htmlparser2](https://github.com/fb55/htmlparser2) - Tokenizer 基於的函式庫\n- [Vue 3.4 Parser Refactor](https://github.com/vuejs/core/pull/9674) - GitHub PR\n"
  },
  {
    "path": "book/online-book/src/zh-tw/50-basic-template-compiler/500-custom-directive.md",
    "content": "# 自訂指令\n\n::: info 關於本章\n本章實現 Vue 的自訂指令功能．\\\n您將學習如何定義像 `v-focus` 這樣的自訂指令，並對元素執行直接操作．\n:::\n\n## 什麼是自訂指令？\n\nVue 的自訂指令是用於對 DOM 元素執行低階操作的功能．當需要進行元件抽象無法處理的直接 DOM 操作時使用．\n\n典型用例：\n\n- 元素自動聚焦（`v-focus`）\n- 點擊外部檢測（`v-click-outside`）\n- 元素延遲載入（`v-lazy`）\n- 工具提示顯示（`v-tooltip`）\n\n```vue\n<script setup>\n// 定義自訂指令\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n</script>\n\n<template>\n  <input v-focus />\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"說實話很少用\">\n\n自訂指令用於「想直接操作 DOM」的場景，但說實話很少被使用．\\\n由於 Vapor Mode 的實現變更以及與靜態分析的兼容性差，**能不用就不用**．\\\n盡量用元件處理能用元件處理的事情！\n\n</KawaikoNote>\n\n## 指令生命週期\n\n指令有類似於元件的生命週期鉤子：\n\n```ts\nconst myDirective = {\n  // 在元素的屬性或事件監聽器套用之前\n  created(el, binding, vnode, prevVnode) {},\n\n  // 在元素插入 DOM 之前\n  beforeMount(el, binding, vnode, prevVnode) {},\n\n  // 在元素插入 DOM 之後\n  mounted(el, binding, vnode, prevVnode) {},\n\n  // 在父元件更新之前\n  beforeUpdate(el, binding, vnode, prevVnode) {},\n\n  // 在父元件和子元件更新之後\n  updated(el, binding, vnode, prevVnode) {},\n\n  // 在父元件卸載之前\n  beforeUnmount(el, binding, vnode, prevVnode) {},\n\n  // 在父元件卸載之後\n  unmounted(el, binding, vnode, prevVnode) {},\n}\n```\n\n每個鉤子接收以下參數：\n\n- `el`：指令綁定的元素\n- `binding`：傳遞給指令的資訊（值，參數等）\n- `vnode`：對應 el 的 VNode\n- `prevVnode`：更新前的 VNode（僅 beforeUpdate，updated）\n\n## 實現概述\n\n自訂指令的實現由三部分組成：\n\n1. **執行時端**：指令類型定義和 `withDirectives` 輔助函數\n2. **渲染器端**：在每個生命週期呼叫鉤子\n3. **編譯器端**：從模板生成 `withDirectives`\n\n## 執行時實現\n\n### 指令類型定義\n\n首先，定義指令類型：\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentPublicInstance | null\n  value: V\n  oldValue: V | null\n  arg?: string\n  dir: ObjectDirective<any>\n}\n\nexport type DirectiveHook<T = any> = (\n  el: T,\n  binding: DirectiveBinding,\n  vnode: VNode,\n  prevVNode: VNode | null\n) => void\n\nexport interface ObjectDirective<T = any> {\n  created?: DirectiveHook<T>\n  beforeMount?: DirectiveHook<T>\n  mounted?: DirectiveHook<T>\n  beforeUpdate?: DirectiveHook<T>\n  updated?: DirectiveHook<T>\n  beforeUnmount?: DirectiveHook<T>\n  unmounted?: DirectiveHook<T>\n}\n```\n\n### withDirectives 輔助函數\n\n編譯器生成將帶有指令的元素用 `withDirectives` 包裝的程式碼：\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport type DirectiveArguments = Array<\n  | [ObjectDirective | undefined]\n  | [ObjectDirective | undefined, any]\n  | [ObjectDirective | undefined, any, string]\n>\n\nexport function withDirectives<T extends VNode>(\n  vnode: T,\n  directives: DirectiveArguments\n): T {\n  const internalInstance = currentRenderingInstance\n  if (internalInstance === null) return vnode\n\n  const instance = internalInstance.proxy\n\n  const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])\n  for (let i = 0; i < directives.length; i++) {\n    let [dir, value, arg] = directives[i]\n    if (dir) {\n      // 將函數形式的指令轉換為物件形式\n      if (isFunction(dir)) {\n        dir = {\n          mounted: dir,\n          updated: dir,\n        } as ObjectDirective\n      }\n      bindings.push({\n        dir,\n        instance,\n        value,\n        oldValue: void 0,\n        arg,\n      })\n    }\n  }\n  return vnode\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"簡單！\">\n\n`withDirectives` 只是給 VNode 新增 `dirs` 屬性．\\\n實際的鉤子呼叫由渲染器完成，所以這個實現只是簡單地將資訊附加到 VNode 上！\n\n</KawaikoNote>\n\n### 呼叫指令鉤子\n\n```ts\n// packages/runtime-core/src/directives.ts\n\nexport function invokeDirectiveHook(\n  vnode: VNode,\n  prevVNode: VNode | null,\n  name: keyof ObjectDirective\n): void {\n  const bindings = vnode.dirs!\n  const oldBindings = prevVNode && prevVNode.dirs!\n\n  for (let i = 0; i < bindings.length; i++) {\n    const binding = bindings[i]\n    // 更新時設定舊值\n    if (oldBindings) {\n      binding.oldValue = oldBindings[i].value\n    }\n\n    const hook = binding.dir[name] as DirectiveHook | undefined\n    if (hook) {\n      hook(vnode.el, binding, vnode, prevVNode)\n    }\n  }\n}\n```\n\n## 渲染器實現\n\n渲染器在元素掛載和更新的各個時機呼叫 `invokeDirectiveHook`：\n\n```ts\n// packages/runtime-core/src/renderer.ts\n\nconst mountElement = (\n  vnode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const { type, props, children, dirs } = vnode\n\n  const el = (vnode.el = hostCreateElement(type as string))\n\n  // 掛載子元素\n  if (typeof children === 'string') {\n    hostSetElementText(el, children)\n  } else if (isArray(children)) {\n    mountChildren(children as VNodeArrayChildren, el, null, parentComponent)\n  }\n\n  // 指令：created 鉤子\n  dirs && invokeDirectiveHook(vnode, null, 'created')\n\n  // 設定 props\n  if (props) {\n    for (const key in props) {\n      hostPatchProp(el, key, null, props[key])\n    }\n  }\n\n  // 指令：beforeMount 鉤子\n  dirs && invokeDirectiveHook(vnode, null, 'beforeMount')\n\n  // 插入 DOM\n  hostInsert(el, container, anchor!)\n\n  // 指令：mounted 鉤子\n  dirs && invokeDirectiveHook(vnode, null, 'mounted')\n}\n\nconst patchElement = (\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n) => {\n  const el = (n2.el = n1.el!)\n  const { dirs } = n2\n  const oldProps = n1.props ?? {}\n  const newProps = n2.props ?? {}\n\n  // 指令：beforeUpdate 鉤子\n  dirs && invokeDirectiveHook(n2, n1, 'beforeUpdate')\n\n  // 更新子元素和 props\n  patchChildren(n1, n2, el, null, parentComponent)\n  patchProps(el, oldProps, newProps)\n\n  // 指令：updated 鉤子\n  dirs && invokeDirectiveHook(n2, n1, 'updated')\n}\n```\n\n## 向 VNode 新增 dirs 屬性\n\n向 VNode 類型定義新增 `dirs`：\n\n```ts\n// packages/runtime-core/src/vnode.ts\n\nexport interface VNode<ExtraProps = { [key: string]: any }> {\n  type: VNodeTypes\n  props: (VNodeProps & ExtraProps) | null\n  children: VNodeNormalizedChildren\n  el: RendererNode | null\n  key: string | number | symbol | null\n  ref: Ref | null\n  shapeFlag: number\n  dirs?: DirectiveBinding[] | null  // 新增\n}\n```\n\n## 編譯器實現\n\n### 註冊 WITH_DIRECTIVES 輔助函數\n\n```ts\n// packages/compiler-core/src/runtimeHelpers.ts\n\nexport const WITH_DIRECTIVES: unique symbol = Symbol()\n\nexport const helperNameMap: Record<symbol, string> = {\n  // ...\n  [WITH_DIRECTIVES]: 'withDirectives',\n}\n```\n\n### 程式碼生成\n\n當 VNode 有指令時，用 `withDirectives` 包裝：\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper } = context\n  const { tag, props, children, directives } = node\n\n  // 如果有指令，用 withDirectives 包裝\n  if (directives) {\n    push(helper(WITH_DIRECTIVES) + `(`)\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`, node)\n  genNodeList(genNullableArgs([tag, props, children]), context)\n  push(`)`)\n\n  if (directives) {\n    push(`, `)\n    genNode(directives, context)\n    push(`)`)\n  }\n}\n```\n\n生成程式碼範例：\n\n```ts\n// 模板：<input v-focus />\n\n// 生成的程式碼\nwithDirectives(\n  createElementVNode('input'),\n  [[vFocus]]\n)\n\n// 模板：<div v-my-directive:arg.modifier=\"value\" />\n\n// 生成的程式碼\nwithDirectives(\n  createElementVNode('div'),\n  [[vMyDirective, value, 'arg', { modifier: true }]]\n)\n```\n\n## 測試\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\n\n// v-focus 指令\nconst vFocus = {\n  mounted(el) {\n    el.focus()\n  }\n}\n\n// v-color 指令\nconst vColor = {\n  mounted(el, binding) {\n    el.style.color = binding.value\n  },\n  updated(el, binding) {\n    el.style.color = binding.value\n  }\n}\n\nconst color = ref('red')\n</script>\n\n<template>\n  <input v-focus placeholder=\"自動聚焦\" />\n\n  <p v-color=\"color\">這段文字是 {{ color }} 色</p>\n\n  <button @click=\"color = 'blue'\">變藍</button>\n  <button @click=\"color = 'green'\">變綠</button>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"實現完成！\">\n\n自訂指令的實現完成了！\\\n透過執行時，渲染器和編譯器的協同工作，現在可以使用像 `v-focus` 這樣的自訂指令了．\\\nv-model 內部也是作為指令實現的，請務必查看！\n\n</KawaikoNote>\n\n## 總結\n\n- 自訂指令是用於直接 DOM 操作的低階 API\n- `withDirectives` 將指令資訊附加到 VNode\n- 渲染器在每個生命週期呼叫鉤子\n- 編譯器從模板生成 `withDirectives`\n\n## 參考連結\n\n- [Vue.js - 自訂指令](https://vuejs.org/guide/reusability/custom-directives.html) - Vue 官方文件\n"
  },
  {
    "path": "book/online-book/src/zh-tw/60-basic-sfc-compiler/010-script-setup.md",
    "content": "# 支援 script setup\n\n::: info 關於本章\n本章介紹如何實現 Vue 3 的 `<script setup>` 語法．\\\n學習 script setup 的工作原理，以更簡潔的方式編寫組件．\n:::\n\n## 什麼是 script setup？\n\n`<script setup>` 是 Vue 3.2 引入的編譯時語法糖．與傳統的 Options API 或 Composition API 相比，它可以更簡潔地編寫組件．\n\n```vue\n<!-- 傳統寫法 -->\n<script>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nexport default {\n  components: { MyComponent },\n  setup() {\n    const count = ref(0)\n    const increment = () => count.value++\n    return { count, increment }\n  }\n}\n</script>\n\n<!-- script setup 寫法 -->\n<script setup>\nimport { ref } from 'chibivue'\nimport MyComponent from './MyComponent.vue'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n```\n\n<KawaikoNote variant=\"surprise\" title=\"簡潔多了！\">\n\n使用 script setup，不需要 `export default` 或 `return`，匯入的組件也會自動註冊．\\\n程式碼變得非常乾淨！\n\n</KawaikoNote>\n\n## 實現概述\n\nscript setup 的編譯包含以下步驟：\n\n1. **匯入分析和提升**：提取 import 語句並移動到檔案頂部\n2. **綁定分析**：追蹤變數宣告和函數定義\n3. **巨集處理**：處理 defineProps，defineEmits 等（後續章節介紹）\n4. **程式碼轉換**：轉換為 setup 函數並生成 return 語句\n\n## compileScript 函數\n\n`compileScript` 函數是編譯 SFC 腳本部分的核心函數．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nexport function compileScript(\n  sfc: SFCDescriptor,\n  options: SFCScriptCompileOptions,\n): SFCScriptBlock {\n  let { script, scriptSetup, source } = sfc\n\n  // 使用 Babel 解析\n  const scriptAst = _parse(script?.content ?? \"\", { sourceType: \"module\" }).program\n  const scriptSetupAst = _parse(scriptSetup?.content ?? \"\", { sourceType: \"module\" }).program\n\n  // 沒有 script setup 時使用傳統處理\n  if (!scriptSetup) {\n    if (!script) {\n      throw new Error(`SFC contains no <script> tags.`)\n    }\n    return { ...script, bindings: analyzeScriptBindings(scriptAst.body) }\n  }\n\n  // 初始化元資料\n  const bindingMetadata: BindingMetadata = {}\n  const userImports: Record<string, ImportBinding> = Object.create(null)\n  const setupBindings: Record<string, BindingTypes> = Object.create(null)\n\n  const s = new MagicString(source)\n  // ... 轉換處理\n}\n```\n\n## 匯入提升\n\nscript setup 內的 import 語句需要移動（提升）到生成程式碼的開頭．\n\n```ts\n// 1.2 walk import declarations of <script setup>\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ImportDeclaration\") {\n    // 將匯入移動到檔案頂部\n    hoistNode(node)\n\n    // 移除重複匯入\n    for (let i = 0; i < node.specifiers.length; i++) {\n      const specifier = node.specifiers[i]\n      const local = specifier.local.name\n      const imported = getImportedName(specifier)\n      const source = node.source.value\n\n      const existing = userImports[local]\n      if (existing) {\n        if (existing.source === source && existing.imported === imported) {\n          removeSpecifier(i)\n        }\n      } else {\n        registerUserImport(source, local, imported, true)\n      }\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"question\" title=\"為什麼需要提升？\">\n\n在生成的程式碼中，import 語句需要放在 `setup()` 函數外部．\\\n提升將 `<script setup>` 內的匯入移動到正確的位置．\n\n順便說一下，`<script setup>` 內的 `export` 會報錯．\\\n但是 `export type` 是可以的，因為它只是類型資訊！\n\n</KawaikoNote>\n\n## 綁定分析\n\n為了正確解析模板中引用的變數，我們需要分析腳本中的綁定．\n\n```ts\nfunction walkDeclaration(\n  node: Declaration,\n  bindings: Record<string, BindingTypes>,\n  userImportAliases: Record<string, string> = {},\n) {\n  if (node.type === \"VariableDeclaration\") {\n    const isConst = node.kind === \"const\"\n\n    for (const { id, init } of node.declarations) {\n      if (id.type === \"Identifier\") {\n        let bindingType\n        if (isConst && isStaticNode(init!)) {\n          bindingType = BindingTypes.LITERAL_CONST\n        } else if (isCallOf(init, userImportAliases[\"reactive\"])) {\n          bindingType = BindingTypes.SETUP_REACTIVE_CONST\n        } else if (isCallOf(init, userImportAliases[\"ref\"])) {\n          bindingType = BindingTypes.SETUP_REF\n        } else if (isConst) {\n          bindingType = BindingTypes.SETUP_MAYBE_REF\n        } else {\n          bindingType = BindingTypes.SETUP_LET\n        }\n        registerBinding(bindings, id, bindingType)\n      }\n    }\n  } else if (node.type === \"FunctionDeclaration\") {\n    bindings[node.id!.name] = BindingTypes.SETUP_CONST\n  }\n}\n```\n\n綁定類型決定了變數在模板中的引用方式：\n\n| 類型 | 描述 | 模板引用 |\n|------|------|---------|\n| `SETUP_REF` | 用 ref() 建立 | 自動新增 `.value` |\n| `SETUP_REACTIVE_CONST` | 用 reactive() 建立 | 直接引用 |\n| `SETUP_CONST` | 常數 | 直接引用 |\n| `SETUP_LET` | let/var 變數 | 直接引用 |\n\n## 內聯模板\n\n使用 script setup 時，模板可以內聯到 setup 函數內部．\n\n```ts\n// 10. generate return statement\nlet returned\nif (options.inlineTemplate) {\n  if (sfc.template) {\n    const { code, preamble } = compileTemplate({\n      source: sfc.template.content.trim(),\n      compilerOptions: { inline: true, bindingMetadata },\n    })\n\n    if (preamble) {\n      s.prepend(preamble)\n    }\n    returned = code\n  } else {\n    returned = `() => {}`\n  }\n}\ns.appendRight(endOffset, `\\nreturn ${returned}\\n`)\n```\n\n生成程式碼範例：\n\n```ts\n// 輸入\n// <script setup>\n// import { ref } from 'chibivue'\n// const count = ref(0)\n// </script>\n// <template>\n//   <p>{{ count }}</p>\n// </template>\n\n// 輸出\nimport { ref } from 'chibivue'\n\nexport default {\n  setup(__props) {\n    const count = ref(0)\n\n    return (_ctx) => {\n      return h('p', count.value)\n    }\n  }\n}\n```\n\n## 與 Vite 外掛整合\n\nVite 外掛檢測並編譯 script setup．\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/script.ts\n\nexport function resolveScript(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n): SFCScriptBlock | null {\n  if (!descriptor.script && !descriptor.scriptSetup) return null\n\n  return options.compiler.compileScript(descriptor, {\n    inlineTemplate: isUseInlineTemplate(descriptor),\n  })\n}\n\nexport function isUseInlineTemplate(descriptor: SFCDescriptor): boolean {\n  return !!descriptor.scriptSetup\n}\n```\n\n## 測試\n\n```vue\n<script setup>\nimport { ref, computed } from 'chibivue'\n\nconst count = ref(0)\nconst double = computed(() => count.value * 2)\n\nconst increment = () => {\n  count.value++\n}\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ count }}</p>\n    <p>Double: {{ double }}</p>\n    <button @click=\"increment\">+1</button>\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"實現完成！\">\n\nscript setup 的基本實現完成了！\\\n與傳統寫法相比，現在可以更簡潔地編寫組件．\\\n下一章我們將學習如何實現 `defineProps` 和 `defineEmits` 巨集．\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/010_script_setup)\n\n## 總結\n\n- `<script setup>` 是更簡潔編寫 Composition API 的語法糖\n- `compileScript` 處理核心轉換邏輯\n- 匯入提升和綁定分析是重要步驟\n- 模板被內聯到 setup 函數內部\n\n## 參考連結\n\n- [Vue.js - script setup](https://vuejs.org/api/sfc-script-setup.html) - Vue 官方文件\n- [RFC: script setup](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/zh-tw/60-basic-sfc-compiler/020-define-props.md",
    "content": "# 支援 defineProps\n\n::: info 關於本章\n本章介紹如何實現 `<script setup>` 中使用的 `defineProps` 巨集．\\\n學習編譯器巨集的工作原理以及 props 宣告的處理方式．\n:::\n\n## 什麼是 defineProps？\n\n`defineProps` 是一個編譯器巨集，用於在 `<script setup>` 內宣告組件的 props．\n\n```vue\n<script setup>\n// 執行時宣告\nconst props = defineProps({\n  title: String,\n  count: {\n    type: Number,\n    default: 0\n  }\n})\n\nconsole.log(props.title)\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"什麼是編譯器巨集？\">\n\n`defineProps` 不是普通函數．它是**編譯器巨集**．\\\n它在編譯時會被特殊處理，在執行時會被擦除．\\\n這就是為什麼不需要匯入就可以使用！\n\n</KawaikoNote>\n\n## 實現概述\n\ndefineProps 的處理包含以下步驟：\n\n1. **檢測巨集呼叫**：在 AST 中找到 `defineProps()` 呼叫\n2. **提取參數**：獲取 props 定義物件\n3. **刪除程式碼**：刪除原始的 `defineProps()` 呼叫\n4. **新增到選項**：作為 `props` 選項新增到輸出\n5. **註冊綁定**：將 props 註冊為 `PROPS` 類型\n\n## processDefineProps 函數\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_PROPS = \"defineProps\"\n\nlet propsRuntimeDecl: Node | undefined\nlet propsIdentifier: string | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  // 儲存參數（props 定義物件）\n  propsRuntimeDecl = node.arguments[0]\n\n  // 如果賦值給變數，儲存識別符\n  // const props = defineProps(...) 中的 \"props\" 部分\n  if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST 遍歷\n\n遍歷 `<script setup>` 的主體來檢測 `defineProps`．\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  // 表達式語句（單獨呼叫 defineProps()）\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr)) {\n      // 刪除巨集呼叫\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  // 變數宣告（const props = defineProps(...)）\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        if (processDefineProps(init, declId)) {\n          // 刪除宣告\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## 註冊 Props 綁定\n\n作為 props 宣告的變數會被註冊到綁定元資料中，以便從模板中引用．\n\n```ts\n// 7. analyze binding metadata\nif (propsRuntimeDecl) {\n  for (const key of getObjectExpressionKeys(propsRuntimeDecl as ObjectExpression)) {\n    bindingMetadata[key] = BindingTypes.PROPS\n  }\n}\n```\n\n通過註冊為 `BindingTypes.PROPS`，模板編譯器可以正確處理對 props 的存取．\n\n## 處理 Props 識別符\n\n當賦值給變數如 `const props = defineProps(...)` 時，需要使該變數可存取．\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\nif (propsIdentifier) {\n  // 新增 const props = __props;\n  s.prependLeft(startOffset, `\\nconst ${propsIdentifier} = __props;\\n`)\n}\n```\n\n## 新增到選項\n\n最終，props 定義作為組件選項輸出．\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  let declCode = scriptSetup.content\n    .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)\n    .trim()\n  runtimeOptions += `\\n  props: ${declCode},`\n}\n\ns.prependLeft(\n  startOffset,\n  `\\nexport default {\\n${runtimeOptions}\\nsetup(${args}) {\\n`\n)\n```\n\n## 轉換結果範例\n\n```vue\n<!-- 輸入 -->\n<script setup>\nconst props = defineProps({\n  title: String,\n  count: Number\n})\n</script>\n\n<template>\n  <h1>{{ title }}</h1>\n</template>\n```\n\n```ts\n// 輸出\nexport default {\n  props: {\n    title: String,\n    count: Number\n  },\n  setup(__props) {\n    const props = __props;\n\n    return (_ctx) => {\n      return h('h1', _ctx.title)\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"簡單！\">\n\n`defineProps` 看起來複雜，但做的事情很簡單：\n1. 將參數移動到 `props` 選項\n2. 刪除 `defineProps()` 呼叫\n3. 如果有變數，替換為對 `__props` 的引用\n\n</KawaikoNote>\n\n## 測試\n\n```vue\n<script setup>\nimport { computed } from 'chibivue'\n\nconst props = defineProps({\n  firstName: String,\n  lastName: String\n})\n\nconst fullName = computed(() => `${props.firstName} ${props.lastName}`)\n</script>\n\n<template>\n  <div>\n    <p>First: {{ firstName }}</p>\n    <p>Last: {{ lastName }}</p>\n    <p>Full: {{ fullName }}</p>\n  </div>\n</template>\n```\n\n父組件：\n\n```vue\n<script setup>\nimport ChildComponent from './ChildComponent.vue'\n</script>\n\n<template>\n  <ChildComponent firstName=\"John\" lastName=\"Doe\" />\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"實現完成！\">\n\ndefineProps 的實現完成了！\\\n現在你理解了編譯器巨集的基本機制．\\\n下一章我們將學習如何實現 `defineEmits` 巨集．\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/020_define_props)\n\n## 總結\n\n- `defineProps` 是編譯器巨集，在編譯時處理\n- 遍歷 AST 檢測 `defineProps()` 呼叫\n- 參數轉換為 `props` 選項，呼叫本身被刪除\n- Props 註冊為 `BindingTypes.PROPS` 以便模板存取\n\n## 參考連結\n\n- [Vue.js - defineProps](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue 官方文件\n"
  },
  {
    "path": "book/online-book/src/zh-tw/60-basic-sfc-compiler/030-define-emits.md",
    "content": "# 支援 defineEmits\n\n::: info 關於本章\n本章介紹如何實現 `<script setup>` 中使用的 `defineEmits` 巨集．\\\n學習子組件向父組件發送事件的機制．\n:::\n\n## 什麼是 defineEmits？\n\n`defineEmits` 是一個編譯器巨集，用於在 `<script setup>` 內宣告組件發出的事件．\n\n```vue\n<script setup>\nconst emit = defineEmits(['change', 'update'])\n\nfunction handleClick() {\n  emit('change', 'new value')\n}\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"與 defineProps 有什麼區別？\">\n\n`defineProps` 用於從父組件向子組件傳遞資料，\\\n`defineEmits` 用於從子組件向父組件通知事件．\\\n記住它們是一對！\n\n</KawaikoNote>\n\n## 實現概述\n\ndefineEmits 的處理與 defineProps 非常相似：\n\n1. **檢測巨集呼叫**：在 AST 中找到 `defineEmits()` 呼叫\n2. **提取參數**：獲取事件定義陣列或物件\n3. **刪除程式碼**：刪除原始的 `defineEmits()` 呼叫\n4. **新增到選項**：作為 `emits` 選項新增到輸出\n5. **提供 emit 函數**：從 setup 的上下文中獲取 `emit`\n\n## processDefineEmits 函數\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nconst DEFINE_EMITS = \"defineEmits\"\n\nlet emitsRuntimeDecl: Node | undefined\nlet emitIdentifier: string | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  // 儲存事件定義\n  emitsRuntimeDecl = node.arguments[0]\n\n  // 如果賦值給變數，儲存識別符\n  // const emit = defineEmits(...) 中的 \"emit\" 部分\n  if (declId) {\n    emitIdentifier =\n      declId.type === \"Identifier\"\n        ? declId.name\n        : scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## AST 遍歷\n\n與 defineProps 類似，遍歷 `<script setup>` 的主體來檢測 `defineEmits`．\n\n```ts\n// 2.2 process <script setup> body\nfor (const node of scriptSetupAst.body) {\n  if (node.type === \"ExpressionStatement\") {\n    const expr = node.expression\n    if (processDefineProps(expr) || processDefineEmits(expr)) {\n      s.remove(node.start! + startOffset, node.end! + startOffset)\n    }\n  }\n\n  if (node.type === \"VariableDeclaration\" && !node.declare) {\n    for (let i = 0; i < node.declarations.length; i++) {\n      const decl = node.declarations[i]\n      const init = decl.init\n      if (init) {\n        const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id\n        const isDefineProps = processDefineProps(init, declId)\n        const isDefineEmits = processDefineEmits(init, declId)\n        if (isDefineProps || isDefineEmits) {\n          s.remove(node.start! + startOffset, node.end! + startOffset)\n        }\n      }\n    }\n  }\n}\n```\n\n## 設定 emit 函數\n\n從 `defineEmits` 獲取的 emit 函數從 setup 函數的第二個參數（SetupContext）中獲取．\n\n```ts\n// 9. finalize setup() argument signature\nlet args = `__props`\n\nconst destructureElements: string[] = []\nif (emitIdentifier) {\n  destructureElements.push(\n    emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`\n  )\n}\n\nif (destructureElements.length) {\n  args += `, { ${destructureElements.join(\", \")} }`\n}\n```\n\n這會生成如下程式碼：\n\n```ts\n// 對於 const emit = defineEmits(['change'])\nsetup(__props, { emit }) {\n  // ...\n}\n\n// 對於 const emitFn = defineEmits(['change'])\nsetup(__props, { emit: emitFn }) {\n  // ...\n}\n```\n\n## 新增到選項\n\n```ts\n// 11. finalize default export\nlet runtimeOptions = ``\nif (propsRuntimeDecl) {\n  runtimeOptions += `\\n  props: ${...},`\n}\nif (emitsRuntimeDecl) {\n  runtimeOptions += `\\n  emits: ${scriptSetup.content\n    .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)\n    .trim()},`\n}\n```\n\n## 轉換結果範例\n\n```vue\n<!-- 輸入 -->\n<script setup>\nconst emit = defineEmits(['update', 'delete'])\n\nfunction handleUpdate(value) {\n  emit('update', value)\n}\n</script>\n\n<template>\n  <button @click=\"handleUpdate('new')\">Update</button>\n</template>\n```\n\n```ts\n// 輸出\nexport default {\n  emits: ['update', 'delete'],\n  setup(__props, { emit }) {\n    function handleUpdate(value) {\n      emit('update', value)\n    }\n\n    return (_ctx) => {\n      return h('button', { onClick: _ctx.handleUpdate.bind(_ctx, 'new') }, 'Update')\n    }\n  }\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"與 defineProps 對稱！\">\n\n`defineEmits` 的實現與 `defineProps` 幾乎相同：\n1. 檢測巨集呼叫\n2. 將參數移動到 `emits` 選項\n3. 如果有變數，轉換為從 SetupContext 獲取\n\n容易記住！\n\n</KawaikoNote>\n\n## 測試\n\n子組件：\n\n```vue\n<script setup>\nconst props = defineProps({\n  modelValue: String\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nfunction updateValue(e) {\n  emit('update:modelValue', e.target.value)\n}\n</script>\n\n<template>\n  <input :value=\"modelValue\" @input=\"updateValue\" />\n</template>\n```\n\n父組件：\n\n```vue\n<script setup>\nimport { ref } from 'chibivue'\nimport CustomInput from './CustomInput.vue'\n\nconst text = ref('')\n</script>\n\n<template>\n  <CustomInput v-model=\"text\" />\n  <p>輸入值: {{ text }}</p>\n</template>\n```\n\n<KawaikoNote variant=\"base\" title=\"實現完成！\">\n\ndefineEmits 的實現完成了！\\\n現在可以使用 props 和 emits 兩個編譯器巨集了．\\\n下一章我們將學習如何實現 scoped CSS．\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/030_define_emits)\n\n## 總結\n\n- `defineEmits` 是宣告子到父事件發送的巨集\n- 處理模式與 `defineProps` 非常相似\n- emit 函數從 SetupContext 解構獲取\n- 作為 `emits` 選項新增到組件\n\n## 參考連結\n\n- [Vue.js - defineEmits](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) - Vue 官方文件\n"
  },
  {
    "path": "book/online-book/src/zh-tw/60-basic-sfc-compiler/040-scoped-css.md",
    "content": "# 支援 Scoped CSS\n\n::: info 關於本章\n本章介紹如何實現 Vue 的 Scoped CSS 功能．\\\n學習如何為每個組件隔離樣式，防止樣式衝突．\n:::\n\n## 什麼是 Scoped CSS？\n\nScoped CSS 是將 `<style scoped>` 中定義的樣式僅應用於該組件的功能．\n\n```vue\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\n此樣式不會影響其他組件中具有相同類名的元素．\n\n<KawaikoNote variant=\"question\" title=\"為什麼需要 Scoped CSS？\">\n\n在大型應用中，不同組件可能使用相同的類名．\\\n沒有 Scoped CSS，樣式可能會意外影響其他組件．\\\n通過為每個組件隔離樣式，可以安全地進行樣式設計！\n\n</KawaikoNote>\n\n## 工作原理\n\nScoped CSS 通過以下步驟實現：\n\n1. **生成作用域 ID**：為每個組件建立唯一 ID\n2. **轉換模板**：為元素新增 `data-v-xxx` 屬性\n3. **轉換樣式**：為選擇器新增 `[data-v-xxx]`\n\n### 轉換範例\n\n```vue\n<!-- 輸入 -->\n<template>\n  <p class=\"message\">Hello</p>\n</template>\n\n<style scoped>\n.message {\n  color: red;\n}\n</style>\n```\n\n```html\n<!-- 輸出 (HTML) -->\n<p class=\"message\" data-v-7ba5bd90>Hello</p>\n\n<!-- 輸出 (CSS) -->\n<style>\n.message[data-v-7ba5bd90] {\n  color: red;\n}\n</style>\n```\n\n## 生成作用域 ID\n\n為每個組件生成唯一 ID．通常使用檔案路徑的雜湊值．\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nimport { createHash } from 'crypto'\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  }\n\n  // 生成作用域 ID\n  descriptor.id = createHash('sha256')\n    .update(filename + source)\n    .digest('hex')\n    .slice(0, 8)\n\n  // ... 其餘解析處理\n}\n```\n\n## 擴展 SFCStyleBlock\n\n為樣式區塊新增 scoped 資訊．\n\n```ts\n// packages/compiler-sfc/src/parse.ts\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\"\n  scoped?: boolean  // 新增\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  // ...\n  node.props.forEach((p) => {\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      attrs[p.name] = p.value ? p.value.content || true : true\n      if (type === \"style\") {\n        if (p.name === \"scoped\") {\n          (block as SFCStyleBlock).scoped = true\n        }\n      }\n    }\n  })\n  return block\n}\n```\n\n## 模板轉換\n\n在模板編譯期間為元素新增 scopeId 屬性．\n\n```ts\n// packages/compiler-core/src/codegen.ts\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper, scopeId } = context\n  const { tag, props, children } = node\n\n  // 如果存在 scopeId，新增到 props\n  let propsWithScope = props\n  if (scopeId) {\n    const scopeIdProp = `\"data-v-${scopeId}\": \"\"`\n    if (props) {\n      // 與現有 props 合併\n      propsWithScope = `{ ...${props}, ${scopeIdProp} }`\n    } else {\n      propsWithScope = `{ ${scopeIdProp} }`\n    }\n  }\n\n  push(helper(CREATE_ELEMENT_VNODE) + `(`)\n  genNodeList(genNullableArgs([tag, propsWithScope, children]), context)\n  push(`)`)\n}\n```\n\n## 樣式轉換\n\n為 CSS 選擇器新增作用域屬性選擇器．\n\n```ts\n// packages/compiler-sfc/src/compileStyle.ts\n\nimport postcss from 'postcss'\n\nexport interface SFCStyleCompileOptions {\n  source: string\n  filename: string\n  id: string\n  scoped?: boolean\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): string {\n  const { source, id, scoped } = options\n\n  if (!scoped) {\n    return source\n  }\n\n  // 使用 PostCSS 轉換選擇器\n  const result = postcss([scopedPlugin(id)]).process(source, { from: undefined })\n  return result.css\n}\n\nfunction scopedPlugin(id: string) {\n  const scopeId = `data-v-${id}`\n\n  return {\n    postcssPlugin: 'vue-sfc-scoped',\n    Rule(rule) {\n      // 為選擇器新增 [data-v-xxx]\n      rule.selectors = rule.selectors.map((selector) => {\n        return `${selector}[${scopeId}]`\n      })\n    },\n  }\n}\n```\n\n## Vite 外掛整合\n\n```ts\n// packages/@extensions/vite-plugin-chibivue/src/main.ts\n\nasync function genStyleCode(descriptor: SFCDescriptor): Promise<string> {\n  let stylesCode = ``\n\n  for (let i = 0; i < descriptor.styles.length; i++) {\n    const style = descriptor.styles[i]\n    const src = descriptor.filename\n    const scoped = style.scoped ? '&scoped=true' : ''\n    const query = `?chibivue&type=style&index=${i}${scoped}&lang.css`\n    const styleRequest = src + query\n    stylesCode += `\\nimport ${JSON.stringify(styleRequest)}`\n  }\n\n  return stylesCode\n}\n\n// 在 Vite 外掛的 load 中編譯樣式\nload(id) {\n  const { filename, query } = parseChibiVueRequest(id)\n  if (query.chibivue && query.type === \"style\") {\n    const descriptor = getDescriptor(filename, options)!\n    const style = descriptor.styles[query.index!]\n\n    if (query.scoped) {\n      return {\n        code: compileStyle({\n          source: style.content,\n          filename,\n          id: descriptor.id,\n          scoped: true,\n        })\n      }\n    }\n\n    return { code: style.content }\n  }\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"PostCSS 的力量！\">\n\n我們使用 PostCSS 進行樣式轉換．\\\nPostCSS 是一個可以將 CSS 作為 AST 處理的工具，使選擇器轉換變得簡單．\\\nVue.js 內部也使用 PostCSS！\n\n</KawaikoNote>\n\n## 測試\n\n```vue\n<!-- ComponentA.vue -->\n<template>\n  <p class=\"text\">Component A</p>\n</template>\n\n<style scoped>\n.text {\n  color: red;\n}\n</style>\n```\n\n```vue\n<!-- ComponentB.vue -->\n<template>\n  <p class=\"text\">Component B</p>\n</template>\n\n<style scoped>\n.text {\n  color: blue;\n}\n</style>\n```\n\n兩個組件使用相同的類名 `.text`，但顯示不同的顏色．\n\n## 特殊選擇器\n\nScoped CSS 支援幾個特殊的選擇器．\n\n### :deep() 選擇器\n\n用於修改子組件的樣式．\n\n```vue\n<style scoped>\n:deep(.child-class) {\n  color: blue;\n}\n</style>\n```\n\n轉換後：\n\n```css\n[data-v-xxx] .child-class {\n  color: blue;\n}\n```\n\n### ::v-slotted() 選擇器\n\n為插槽內容套用樣式．\n\n```vue\n<style scoped>\n::v-slotted(.slot-content) {\n  font-weight: bold;\n}\n</style>\n```\n\n轉換後：\n\n```css\n.slot-content[data-v-xxx-s] {\n  font-weight: bold;\n}\n```\n\n`-s` 後綴表示「slotted（插槽）」．\n由於插槽內容來自父組件，\n使用特殊的插槽作用域 ID 而不是常規的作用域 ID．\n\n### :global() 選擇器\n\n在 scoped 樣式區塊中定義全域樣式．\n\n```vue\n<style scoped>\n:global(.global-class) {\n  margin: 0;\n}\n</style>\n```\n\n轉換後：\n\n```css\n.global-class {\n  margin: 0;\n}\n```\n\n## 使用 v-bind() 的動態樣式\n\n可以在 CSS 中使用組件狀態．\n\n```vue\n<script setup>\nimport { ref } from 'vue'\nconst color = ref('red')\n</script>\n\n<style scoped>\n.text {\n  color: v-bind(color);\n}\n</style>\n```\n\n轉換後：\n\n```css\n.text[data-v-xxx] {\n  color: var(--xxx-color);\n}\n```\n\n`v-bind()` 被轉換為 CSS 自訂屬性（CSS 變數）．\n在執行時，CSS 變數的值作為組件的內聯樣式設定．\n\n### 使用複雜表達式\n\n透過引號包裹可以使用複雜的表達式．\n\n```vue\n<style scoped>\n.box {\n  width: v-bind('size + \"px\"');\n  background: v-bind('theme.colors.primary');\n}\n</style>\n```\n\n<KawaikoNote variant=\"warning\" title=\"v-bind() 的效能考量\">\n\n`v-bind()` 是一個方便的功能，但有效能影響：\n\n- 每個 `v-bind()` 作為 CSS 自訂屬性設定在內聯樣式中\n- 每次值更改時都會觸發樣式重新計算\n- 對於頻繁更改的值，直接使用內聯樣式可能更有效率\n\n對於動畫或頻繁更新，請考慮使用內聯樣式或 CSS 動畫代替 `v-bind()`．\n\n</KawaikoNote>\n\n## 未來擴展\n\n還可以考慮以下功能：\n\n- **CSS Modules**：自動類名生成\n- **CSS-in-JS 整合**：增強動態樣式\n\n<KawaikoNote variant=\"base\" title=\"嘗試實現！\">\n\n參考本章介紹的原理，嘗試自己實現 Scoped CSS！\\\n這也是學習如何使用 PostCSS 的好機會．\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/040_scoped_css)\n\n## 總結\n\n- Scoped CSS 為每個組件隔離樣式\n- 生成唯一的 scopeId 並應用於模板和樣式\n- 模板獲得 `data-v-xxx` 屬性，CSS 獲得 `[data-v-xxx]` 選擇器\n- 使用 PostCSS 轉換選擇器\n\n## 參考連結\n\n- [Vue.js - Scoped CSS](https://vuejs.org/api/sfc-css-features.html#scoped-css) - Vue 官方文件\n- [PostCSS](https://postcss.org/) - CSS 轉換工具\n"
  },
  {
    "path": "book/online-book/src/zh-tw/60-basic-sfc-compiler/050-props-destructure.md",
    "content": "# 支援 Props 解構\n\n::: info 關於本章\n本章介紹如何實現 Vue 3.5 的響應式 Props 解構功能．\\\n學習如何在解構 props 的同時保持響應性．\n:::\n\n## 什麼是響應式 Props 解構？\n\n從 Vue 3.5 開始，你可以在 `<script setup>` 中解構 `defineProps` 的回傳值．\n\n```vue\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\n這個功能使存取 props 更加簡單．\n\n<KawaikoNote variant=\"question\" title=\"為什麼需要特殊處理？\">\n\n在普通的 JavaScript 中，解構物件會複製值，並斷開與原始物件的連接．\\\n但是 Vue 的 props 需要保持響應性．\\\n編譯器將解構存取轉換為 `__props.xxx` 存取來保持響應性！\n\n</KawaikoNote>\n\n## 工作原理\n\nProps 解構通過以下步驟實現：\n\n1. **模式檢測**：檢測 `const { ... } = defineProps(...)`\n2. **綁定註冊**：將每個解構的屬性註冊為 `PROPS`\n3. **預設值處理**：將預設值轉換為 `withDefaults` 等效處理\n4. **程式碼轉換**：將 props 存取轉換為 `__props.xxx`\n\n### 轉換範例\n\n```vue\n<!-- 輸入 -->\n<script setup>\nconst { count, message = 'hello' } = defineProps({\n  count: Number,\n  message: String\n})\n\nconsole.log(count, message)\n</script>\n```\n\n```ts\n// 輸出\nexport default {\n  props: {\n    count: Number,\n    message: { type: String, default: 'hello' }\n  },\n  setup(__props) {\n    console.log(__props.count, __props.message)\n\n    return (_ctx) => {\n      // ...\n    }\n  }\n}\n```\n\n## 檢測解構模式\n\n檢測 `defineProps` 的回傳值是否被賦值給 `ObjectPattern`（解構模式）．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\ninterface PropsDestructureBindings {\n  [key: string]: {\n    local: string      // 本地變數名\n    default?: string   // 預設值\n  }\n}\n\nlet propsDestructuredBindings: PropsDestructureBindings = Object.create(null)\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  propsRuntimeDecl = node.arguments[0]\n\n  // 處理解構模式\n  if (declId && declId.type === \"ObjectPattern\") {\n    processPropsDestructure(declId)\n  } else if (declId) {\n    propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)\n  }\n\n  return true\n}\n```\n\n## 處理解構\n\n從 `ObjectPattern` 中提取每個屬性並註冊為綁定．\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"ObjectProperty\") {\n      const key = prop.key\n      const value = prop.value\n\n      // 獲取屬性名\n      let propKey: string\n      if (key.type === \"Identifier\") {\n        propKey = key.name\n      } else if (key.type === \"StringLiteral\") {\n        propKey = key.value\n      } else {\n        continue\n      }\n\n      // 處理本地變數名和預設值\n      let local: string\n      let defaultValue: string | undefined\n\n      if (value.type === \"Identifier\") {\n        // const { count } = defineProps(...)\n        local = value.name\n      } else if (value.type === \"AssignmentPattern\") {\n        // const { count = 0 } = defineProps(...)\n        if (value.left.type === \"Identifier\") {\n          local = value.left.name\n          defaultValue = scriptSetup!.content.slice(\n            value.right.start!,\n            value.right.end!\n          )\n        } else {\n          continue\n        }\n      } else {\n        continue\n      }\n\n      // 註冊綁定\n      propsDestructuredBindings[propKey] = { local, default: defaultValue }\n      bindingMetadata[local] = BindingTypes.PROPS\n    }\n  }\n}\n```\n\n## 預設值處理\n\n當在解構中指定預設值時，將其合併到 props 定義中．\n\n```ts\nfunction genRuntimeProps(): string | undefined {\n  if (!propsRuntimeDecl) return undefined\n\n  let propsString = scriptSetup!.content.slice(\n    propsRuntimeDecl.start!,\n    propsRuntimeDecl.end!\n  )\n\n  // 如果有預設值則合併\n  const defaults: Record<string, string> = {}\n  for (const key in propsDestructuredBindings) {\n    const binding = propsDestructuredBindings[key]\n    if (binding.default) {\n      defaults[key] = binding.default\n    }\n  }\n\n  if (Object.keys(defaults).length > 0) {\n    // 相當於 withDefaults 的處理\n    propsString = mergeDefaults(propsString, defaults)\n  }\n\n  return propsString\n}\n\nfunction mergeDefaults(\n  propsString: string,\n  defaults: Record<string, string>\n): string {\n  // 實際實現通過操作 AST 來合併預設值\n  // 這裡是簡化範例\n  const ast = parseExpression(propsString)\n  // ... 合併預設值的處理\n  return generate(ast).code\n}\n```\n\n## 轉換 Props 存取\n\n在模板和腳本中，將解構變數的存取轉換為 `__props.xxx`．\n\n```ts\nfunction processPropsAccess(source: string): string {\n  const s = new MagicString(source)\n\n  // 遍歷識別符並轉換\n  walk(scriptSetupAst, {\n    enter(node: Node) {\n      if (node.type === \"Identifier\") {\n        const binding = propsDestructuredBindings[node.name]\n        if (binding && binding.local === node.name) {\n          // 轉換為 props 存取\n          s.overwrite(node.start!, node.end!, `__props.${node.name}`)\n        }\n      }\n    }\n  })\n\n  return s.toString()\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"編譯器的魔法！\">\n\n解構在普通 JavaScript 中通常會失去響應性，\\\n但編譯器將其轉換為 `__props.xxx` 存取，\\\n使你可以將解構語法作為語法糖使用！\n\n</KawaikoNote>\n\n## Rest 模式支援\n\n也可以支援 `...rest` 模式．\n\n```vue\n<script setup>\nconst { id, ...attrs } = defineProps(['id', 'class', 'style'])\n</script>\n```\n\n```ts\nfunction processPropsDestructure(pattern: ObjectPattern) {\n  for (const prop of pattern.properties) {\n    if (prop.type === \"RestElement\") {\n      // 處理 rest 模式\n      if (prop.argument.type === \"Identifier\") {\n        const restName = prop.argument.name\n        // rest 需要特殊處理\n        // 實際上使用 computed 來獲取剩餘的 props\n        bindingMetadata[restName] = BindingTypes.SETUP_REACTIVE_CONST\n      }\n    }\n    // ...\n  }\n}\n```\n\n## 測試\n\n```vue\n<!-- Parent.vue -->\n<script setup>\nimport { ref } from 'chibivue'\nimport Child from './Child.vue'\n\nconst count = ref(0)\nconst message = ref('Hello')\n</script>\n\n<template>\n  <Child :count=\"count\" :message=\"message\" />\n  <button @click=\"count++\">Increment</button>\n</template>\n```\n\n```vue\n<!-- Child.vue -->\n<script setup>\nconst { count, message = 'default' } = defineProps({\n  count: Number,\n  message: String\n})\n\n// count 和 message 被轉換為 __props.count, __props.message\nconsole.log(count, message)\n</script>\n\n<template>\n  <p>{{ count }} - {{ message }}</p>\n</template>\n```\n\n## 未來擴展\n\n可以考慮以下功能：\n\n- **別名支援**：支援 `const { count: c } = defineProps(...)`\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/050_props_destructure)\n\n## 總結\n\n- Props 解構是 Vue 3.5 引入的功能\n- 檢測解構模式並將每個屬性註冊為 `PROPS` 綁定\n- 預設值合併到 props 定義中\n- 將變數存取轉換為 `__props.xxx` 以保持響應性\n\n## 參考連結\n\n- [Vue.js - 響應式 Props 解構](https://vuejs.org/guide/components/props.html#reactive-props-destructure) - Vue 官方文件\n- [RFC - Reactive Props Destructure](https://github.com/vuejs/rfcs/discussions/502) - Vue RFC\n"
  },
  {
    "path": "book/online-book/src/zh-tw/60-basic-sfc-compiler/060-type-based-macros.md",
    "content": "# 基於類型的 defineProps / defineEmits\n\n::: info 關於本章\n本章介紹如何使用 TypeScript 類型參數實現 `defineProps` 和 `defineEmits`．\\\n學習如何從類型定義生成執行時定義．\n:::\n\n## 什麼是基於類型的宣告？\n\n在 Vue 3 中，你可以使用 TypeScript 泛型宣告 `defineProps` 和 `defineEmits`．\n\n```vue\n<script setup lang=\"ts\">\n// 基於類型的 defineProps\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n\n// 基於類型的 defineEmits\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n<KawaikoNote variant=\"question\" title=\"為什麼基於類型更方便？\">\n\n執行時宣告使用 `Number`，`String` 等，\\\n但基於類型的宣告可以直接使用 TypeScript 的類型系統！\\\nIDE 的補全和錯誤檢查也更加強大．\n\n</KawaikoNote>\n\n## 工作原理\n\n基於類型的巨集通過以下步驟處理：\n\n1. **類型參數檢測**：檢測 `defineProps<T>()` 中的泛型\n2. **類型解析**：解析 TypeScript 類型定義\n3. **執行時定義生成**：從類型生成執行時 props/emits\n4. **程式碼輸出**：作為普通執行時宣告輸出\n\n### 轉換範例\n\n```vue\n<!-- 輸入 -->\n<script setup lang=\"ts\">\nconst props = defineProps<{\n  count: number\n  message?: string\n}>()\n</script>\n```\n\n```ts\n// 輸出\nexport default {\n  props: {\n    count: { type: Number, required: true },\n    message: { type: String, required: false }\n  },\n  setup(__props) {\n    // ...\n  }\n}\n```\n\n## 檢測類型參數\n\n檢測 `defineProps` 或 `defineEmits` 是否有類型參數．\n\n```ts\n// packages/compiler-sfc/src/compileScript.ts\n\nlet propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined\n\nfunction processDefineProps(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_PROPS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  // 檢查類型參數\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    if (typeArg) {\n      propsTypeDecl = resolveTypeElements(typeArg)\n    }\n  } else {\n    // 執行時宣告\n    propsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n```\n\n## 解析類型\n\n解析 TypeScript 類型字面量以提取屬性資訊．\n\n```ts\ninterface PropTypeData {\n  type: string[]      // 類型陣列（支援聯合類型）\n  required: boolean   // 是否必需\n}\n\nfunction extractPropsFromType(\n  typeDecl: TSTypeLiteral | TSInterfaceBody\n): Record<string, PropTypeData> {\n  const props: Record<string, PropTypeData> = {}\n\n  const members = typeDecl.type === \"TSTypeLiteral\"\n    ? typeDecl.members\n    : typeDecl.body\n\n  for (const member of members) {\n    if (member.type === \"TSPropertySignature\") {\n      const key = member.key\n      if (key.type !== \"Identifier\") continue\n\n      const propName = key.name\n      const isOptional = !!member.optional\n\n      // 解析類型\n      const types = member.typeAnnotation\n        ? resolveType(member.typeAnnotation.typeAnnotation)\n        : [\"null\"]\n\n      props[propName] = {\n        type: types,\n        required: !isOptional\n      }\n    }\n  }\n\n  return props\n}\n```\n\n## 類型到建構函數的轉換\n\n將 TypeScript 類型轉換為 JavaScript 建構函數．\n\n```ts\nfunction resolveType(node: TSType): string[] {\n  switch (node.type) {\n    case \"TSStringKeyword\":\n      return [\"String\"]\n\n    case \"TSNumberKeyword\":\n      return [\"Number\"]\n\n    case \"TSBooleanKeyword\":\n      return [\"Boolean\"]\n\n    case \"TSArrayType\":\n      return [\"Array\"]\n\n    case \"TSFunctionType\":\n      return [\"Function\"]\n\n    case \"TSObjectKeyword\":\n    case \"TSTypeLiteral\":\n      return [\"Object\"]\n\n    case \"TSUnionType\":\n      // 聯合類型返回多個建構函數\n      const types: string[] = []\n      for (const t of node.types) {\n        // 排除 null/undefined\n        if (t.type === \"TSNullKeyword\" || t.type === \"TSUndefinedKeyword\") {\n          continue\n        }\n        types.push(...resolveType(t))\n      }\n      return types\n\n    case \"TSTypeReference\":\n      // 自訂類型和參照\n      if (node.typeName.type === \"Identifier\") {\n        const name = node.typeName.name\n        // 內建類型對映\n        if (name === \"Array\") return [\"Array\"]\n        if (name === \"Function\") return [\"Function\"]\n        if (name === \"Object\") return [\"Object\"]\n        // 其他保持原樣\n        return [name]\n      }\n      return [\"Object\"]\n\n    default:\n      return [\"null\"]\n  }\n}\n```\n\n## 生成執行時定義\n\n從解析的類型資訊生成執行時 props 定義．\n\n```ts\nfunction genRuntimePropsFromType(\n  propsDecl: Record<string, PropTypeData>\n): string {\n  const props: string[] = []\n\n  for (const [key, { type, required }] of Object.entries(propsDecl)) {\n    const typeStr = type.length === 1\n      ? type[0]\n      : `[${type.join(\", \")}]`\n\n    if (required) {\n      props.push(`${key}: { type: ${typeStr}, required: true }`)\n    } else {\n      props.push(`${key}: { type: ${typeStr}, required: false }`)\n    }\n  }\n\n  return `{ ${props.join(\", \")} }`\n}\n```\n\n## defineEmits 的類型處理\n\n`defineEmits` 同樣處理類型參數．\n\n```ts\nlet emitsTypeDecl: TSFunctionType[] | undefined\n\nfunction processDefineEmits(node: Node, declId?: LVal): boolean {\n  if (!isCallOf(node, DEFINE_EMITS)) {\n    return false\n  }\n\n  const callExpr = node as CallExpression\n\n  if (callExpr.typeParameters) {\n    const typeArg = callExpr.typeParameters.params[0]\n    emitsTypeDecl = resolveEmitsTypeElements(typeArg)\n  } else {\n    emitsRuntimeDecl = node.arguments[0]\n  }\n\n  // ...\n  return true\n}\n\nfunction resolveEmitsTypeElements(\n  typeArg: TSType\n): TSFunctionType[] | undefined {\n  // 函數多載形式\n  if (typeArg.type === \"TSTypeLiteral\") {\n    return typeArg.members\n      .filter((m): m is TSCallSignatureDeclaration =>\n        m.type === \"TSCallSignatureDeclaration\"\n      )\n      .map(m => m as unknown as TSFunctionType)\n  }\n  return undefined\n}\n```\n\n## 生成 emits 執行時定義\n\n```ts\nfunction genRuntimeEmitsFromType(\n  emitsDecl: TSFunctionType[]\n): string {\n  const events: string[] = []\n\n  for (const sig of emitsDecl) {\n    // 第一個參數是事件名\n    const firstParam = sig.parameters?.[0]\n    if (firstParam?.type === \"Identifier\" && firstParam.typeAnnotation) {\n      const typeAnn = firstParam.typeAnnotation.typeAnnotation\n      if (typeAnn.type === \"TSLiteralType\" &&\n          typeAnn.literal.type === \"StringLiteral\") {\n        events.push(`\"${typeAnn.literal.value}\"`)\n      }\n    }\n  }\n\n  return `[${events.join(\", \")}]`\n}\n```\n\n### 轉換範例\n\n```vue\n<!-- 輸入 -->\n<script setup lang=\"ts\">\nconst emit = defineEmits<{\n  (e: 'change', value: string): void\n  (e: 'update', id: number): void\n}>()\n</script>\n```\n\n```ts\n// 輸出\nexport default {\n  emits: ['change', 'update'],\n  setup(__props, { emit }) {\n    // ...\n  }\n}\n```\n\n## withDefaults 支援\n\n要為基於類型的 props 指定預設值，使用 `withDefaults`．\n\n```vue\n<script setup lang=\"ts\">\ninterface Props {\n  count: number\n  message?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  message: 'default message'\n})\n</script>\n```\n\n```ts\nconst WITH_DEFAULTS = \"withDefaults\"\n\nfunction processWithDefaults(node: Node): boolean {\n  if (!isCallOf(node, WITH_DEFAULTS)) {\n    return false\n  }\n\n  const [propsCall, defaultsArg] = node.arguments\n\n  // 處理 defineProps\n  if (isCallOf(propsCall, DEFINE_PROPS)) {\n    processDefineProps(propsCall)\n  }\n\n  // 儲存預設值\n  if (defaultsArg) {\n    propsDefaults = defaultsArg\n  }\n\n  return true\n}\n```\n\n## 測試\n\n```vue\n<!-- TypedComponent.vue -->\n<script setup lang=\"ts\">\ninterface Props {\n  id: number\n  name: string\n  active?: boolean\n}\n\ninterface Emits {\n  (e: 'select', id: number): void\n  (e: 'update', name: string): void\n}\n\nconst props = defineProps<Props>()\nconst emit = defineEmits<Emits>()\n\nfunction handleClick() {\n  emit('select', props.id)\n}\n</script>\n\n<template>\n  <div @click=\"handleClick\">\n    {{ name }} ({{ active ? 'active' : 'inactive' }})\n  </div>\n</template>\n```\n\n## 未來擴展\n\n可以考慮以下功能：\n\n- **介面參照**：參照其他檔案中定義的類型\n- **對映類型**：`Partial<T>` 等變換類型\n- **泛型組件**：帶有泛型類型參數的組件\n- **僅類型導入**：處理 `import type`\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/60_basic_sfc_compiler/060_type_based_macros)\n\n## 總結\n\n- 基於類型的 defineProps/defineEmits 使用 TypeScript 類型參數\n- 編譯器解析類型並生成執行時定義\n- TypeScript 類型對映到 JavaScript 建構函數\n- 可以使用 withDefaults 指定預設值\n\n## 參考連結\n\n- [Vue.js - 組合式 API 與 TypeScript](https://vuejs.org/guide/typescript/composition-api.html) - Vue 官方文件\n- [Vue.js - 僅類型 props/emit 宣告](https://vuejs.org/api/sfc-script-setup.html#type-only-props-emit-declarations) - Vue 官方文件\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/010-plugins/010-router.md",
    "content": "# 路由器\n\n## 什麼是路由器？\n\n在單頁應用（SPA）中，我們需要根據 URL 顯示不同的組件．在 Vue.js 生態系統中，Vue Router 提供了這個功能．\n\n<KawaikoNote variant=\"question\" title=\"SPA 路由？\">\n\n在傳統網站中，每次 URL 變化時都會從伺服器獲取新的 HTML 頁面．\n在 SPA 中，頁面切換由 JavaScript 處理，無需請求伺服器即可更新螢幕．\n這被稱為「客戶端路由」．\n\n</KawaikoNote>\n\n在本章中，我們將實現基本的 Vue Router 功能，命名為 chibivue-router．\n\n## 套件結構\n\nchibivue-router 位於 `@extensions/chibivue-router` 套件中．\n\n```\n@extensions/chibivue-router/src/\n├── index.ts              # 匯出\n├── router.ts             # 主路由邏輯\n├── history.ts            # History API 封裝\n├── RouterView.ts         # RouterView 組件\n├── useApi.ts             # Composition API 鉤子\n├── injectionSymbols.ts   # 依賴注入鍵\n└── types/\n    └── index.ts          # 類型定義\n```\n\n## 類型定義\n\n### RouteLocationNormalizedLoaded\n\n表示當前路由資訊的類型．\n\n```ts\n// types/index.ts\nexport interface RouteLocationNormalizedLoaded {\n  fullPath: string;\n  component: any;\n}\n```\n\n### RouteRecord\n\n表示路由定義的類型．\n\n```ts\n// router.ts\nexport interface RouteRecord {\n  path: string;\n  component: any;\n}\n```\n\n### Router 介面\n\n定義路由器的公開 API．\n\n```ts\n// router.ts\nexport interface Router {\n  install(app: App): void;\n  push(to: string): void;\n  replace(to: string): void;\n}\n```\n\n## History API 抽象\n\n封裝瀏覽器的 History API，使其更易於在路由器中使用．\n\n### RouterHistory 介面\n\n```ts\n// history.ts\nexport interface RouterHistory {\n  location: Location;\n  push(to: string): void;\n  replace(to: string): void;\n  go(delta: number, triggerListeners?: boolean): void;\n}\n```\n\n### createWebHistory 函數\n\n```ts\n// history.ts\nexport const createWebHistory = (): RouterHistory => {\n  return {\n    location: window.location,\n    push(to: string) {\n      window.history.pushState({}, \"\", to);\n    },\n    replace(to: string) {\n      window.history.replaceState({}, \"\", to);\n    },\n    go(delta: number, triggerListeners?: boolean) {\n      window.history.go(delta);\n    },\n  };\n};\n```\n\n要點：\n- `pushState`：向歷史記錄添加新條目（可以用返回按鈕返回）\n- `replaceState`：替換當前歷史記錄條目（不會保留在歷史記錄中）\n- `go`：在歷史記錄中前進或後退\n\n<KawaikoNote variant=\"funny\" title=\"pushState vs replaceState\">\n\n可以把 `pushState` 想像成「在書架上添加一本新書」．\n`replaceState` 就像「用另一本書替換你正在讀的書」．\n返回按鈕就像「回到你之前讀的書」．\n\n</KawaikoNote>\n\n## 依賴注入鍵\n\n定義用於通過 provide/inject 共享路由相關值的鍵．\n\n```ts\n// injectionSymbols.ts\nimport type { ComputedRef, InjectionKey, Ref } from \"@chibivue/runtime-core\";\nimport type { Router } from \"./router\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\n\n// 路由器本身\nexport const routerKey = Symbol() as InjectionKey<Router>;\n\n// 當前路由（包裝在 computed 中）\nexport const routeLocationKey = Symbol() as InjectionKey<\n  ComputedRef<RouteLocationNormalizedLoaded>\n>;\n\n// RouterView 用的路由（Ref）\nexport const routerViewLocationKey = Symbol() as InjectionKey<\n  Ref<RouteLocationNormalizedLoaded>\n>;\n```\n\n需要三個獨立鍵的原因：\n1. `routerKey`：用於存取導航方法（`push`，`replace`）\n2. `routeLocationKey`：用於通過 `useRoute()` 獲取當前路由資訊（通過 computed 實現響應式）\n3. `routerViewLocationKey`：用於 RouterView 組件確定顯示哪個組件\n\n## createRouter 實現\n\n### 路由解析\n\n```ts\n// router.ts\nconst resolve = (to: string) => {\n  const route = options.routes.find((route) => route.path === to);\n  return {\n    fullPath: to,\n    component: route?.component ?? null,\n  };\n};\n```\n\n當前實現僅支援精確匹配．Vue Router 的實際實現還支援參數（`/user/:id`）和正規表達式．\n\n### 狀態管理\n\n```ts\n// router.ts\nconst currentRoute = ref<RouteLocationNormalizedLoaded>({\n  fullPath: routerHistory.location.pathname,\n  component: resolve(routerHistory.location.pathname).component,\n});\n```\n\n當前路由資訊使用 `ref` 管理．這使得路由變化時 RouterView 可以自動重新渲染．\n\n### 導航方法\n\n```ts\n// router.ts\nfunction push(to: string) {\n  routerHistory.push(to);\n  currentRoute.value = resolve(to);\n}\n\nfunction replace(to: string) {\n  routerHistory.replace(to);\n  currentRoute.value = resolve(to);\n}\n```\n\n同時更改 URL 和響應式狀態．\n\n### 外掛安裝\n\n```ts\n// router.ts\ninstall(app: App) {\n  const router = this;\n\n  // 全域註冊 RouterView 組件\n  app.component(\"RouterView\", RouterViewImpl);\n\n  // 建立響應式路由資訊\n  const reactiveRoute = computed(() => currentRoute.value);\n\n  // 提供值\n  app.provide(routerKey, router);\n  app.provide(routeLocationKey, reactive(reactiveRoute));\n  app.provide(routerViewLocationKey, currentRoute);\n}\n```\n\n當呼叫 `app.use(router)` 時，會執行這個 `install` 方法．\n\n## RouterView 組件\n\n顯示與當前路由對應的組件．\n\n```ts\n// RouterView.ts\nimport { type ComponentOptions, Fragment, h, inject } from \"chibivue\";\nimport { routerViewLocationKey } from \"./injectionSymbols\";\n\nexport const RouterViewImpl: ComponentOptions = {\n  name: \"RouterView\",\n  setup() {\n    const injectedRoute = inject(routerViewLocationKey)!;\n\n    return () => {\n      const ViewComponent = injectedRoute.value.component;\n\n      // 包裝在 Fragment 中進行渲染\n      const component = h(Fragment, [\n        h(ViewComponent, { key: injectedRoute.value.fullPath }),\n      ]);\n\n      return component;\n    };\n  },\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"key 屬性很重要！\">\n\n通過指定 `fullPath` 作為 `key`，每當路由變化時組件都會完全重新掛載．\n如果沒有這個，相同的組件會被重用，`setup` 不會重新執行．\n\n</KawaikoNote>\n\n包裝在 Fragment 中的原因是為了確保正確的子元素補丁行為．\n\n## Composition API 鉤子\n\n### useRouter\n\n獲取路由器實例．\n\n```ts\n// useApi.ts\nexport function useRouter(): Router {\n  return inject(routerKey)!;\n}\n```\n\n用法：\n```ts\nconst router = useRouter()\nrouter.push('/about')\n```\n\n### useRoute\n\n獲取當前路由資訊．\n\n```ts\n// useApi.ts\nexport function useRoute(): ComputedRef<RouteLocationNormalizedLoaded> {\n  return inject(routeLocationKey)!;\n}\n```\n\n用法：\n```ts\nconst route = useRoute()\nconsole.log(route.value.fullPath) // '/about'\n```\n\n## 使用範例\n\n### 路由器設定\n\n```ts\n// router.ts\nimport { createRouter, createWebHistory } from 'chibivue-router'\nimport Home from './pages/Home.vue'\nimport About from './pages/About.vue'\nimport Contact from './pages/Contact.vue'\n\nexport const router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '/', component: Home },\n    { path: '/about', component: About },\n    { path: '/contact', component: Contact },\n  ],\n})\n```\n\n### 註冊到應用程式\n\n```ts\n// main.ts\nimport { createApp } from 'chibivue'\nimport App from './App.vue'\nimport { router } from './router'\n\nconst app = createApp(App)\napp.use(router)\napp.mount('#app')\n```\n\n### 在模板中使用\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { useRouter } from 'chibivue-router'\n\nconst router = useRouter()\n</script>\n\n<template>\n  <header>\n    <nav>\n      <button @click=\"router.push('/')\">首頁</button>\n      <button @click=\"router.push('/about')\">關於</button>\n      <button @click=\"router.push('/contact')\">聯絡</button>\n    </nav>\n  </header>\n\n  <main>\n    <RouterView />\n  </main>\n</template>\n```\n\n## 處理流程\n\n```\napp.use(router)\n  ↓\nrouter.install(app)\n  ├── app.component(\"RouterView\", RouterViewImpl)\n  ├── app.provide(routerKey, router)\n  ├── app.provide(routeLocationKey, ...)\n  └── app.provide(routerViewLocationKey, currentRoute)\n  ↓\nRouterView 渲染\n  ↓\ninject(routerViewLocationKey) 獲取 currentRoute\n  ↓\n渲染 currentRoute.value.component\n\n--- 導航 ---\n\nrouter.push('/about')\n  ↓\nrouterHistory.push('/about')  ← URL 變化\n  ↓\ncurrentRoute.value = resolve('/about')  ← 狀態更新\n  ↓\nRouterView 重新渲染\n  ↓\n顯示新組件\n```\n\n## 未來擴展\n\n當前實現是最小化的，但 Vue Router 還有以下功能：\n\n1. **RouterLink 組件**：包裝 `<a>` 標籤的導航組件\n2. **路由參數**：動態片段如 `/user/:id`\n3. **查詢參數**：解析 `?key=value`\n4. **導航守衛**：`beforeEach`，`afterEach` 等鉤子\n5. **popstate 事件**：處理瀏覽器返回/前進按鈕\n6. **巢狀路由**：定義子路由\n\n<KawaikoNote variant=\"surprise\" title=\"實現完成！\">\n\n我們完成了一個簡單的路由器．\n用大約 100 行程式碼，我們實現了 SPA 路由．\n這應該是理解 Vue Router 工作原理的一個好起點．\n\n</KawaikoNote>\n\n## 總結\n\nchibivue-router 實現由以下部分組成：\n\n1. **History API 封裝**：用 `createWebHistory` 抽象瀏覽器歷史操作\n2. **響應式狀態管理**：用 `ref` 管理當前路由\n3. **依賴注入**：通過 `provide/inject` 在組件樹中共享路由資訊\n4. **RouterView 組件**：動態顯示與當前路由對應的組件\n5. **Composition API 鉤子**：通過 `useRouter` 和 `useRoute` 輕鬆存取\n\n通過結合 Vue 的外掛系統，provide/inject 和響應式系統，我們實現了客戶端路由．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/010-plugins/020-preprocessors.md",
    "content": "# CSS 預處理器\n\n## 什麼是預處理器？\n\nCSS 預處理器是將擴展的 CSS 語言（SCSS，Less，Stylus 等）轉換為標準 CSS 的工具．這些語言提供了變數，巢狀，混入和函數等功能，使 CSS 編寫更加高效．\n\n<KawaikoNote variant=\"question\" title=\"為什麼使用預處理器？\">\n\n純 CSS 有一些限制：\n- 沒有變數（CSS 自訂屬性是後來添加的）\n- 沒有巢狀\n- 程式碼複用困難\n\n預處理器解決了這些問題，使編寫可維護的樣式表成為可能．\n\n</KawaikoNote>\n\n在 Vue SFC 中，你可以通過在 `<style>` 區塊上指定 `lang` 屬性來使用預處理器．\n\n```vue\n<style lang=\"scss\">\n$primary-color: #42b883;\n\n.container {\n  .title {\n    color: $primary-color;\n  }\n}\n</style>\n```\n\n## 支援的預處理器\n\nVue/chibivue 支援以下預處理器：\n\n| 預處理器 | lang 屬性 | 特點 |\n|---------|----------|------|\n| **SCSS** | `scss` | 類 CSS 語法，變數，巢狀，混入 |\n| **Sass** | `sass` | 基於縮排的語法（無花括號）|\n| **Less** | `less` | 變數（`@`），混入，函數 |\n| **Stylus** | `styl`, `stylus` | 靈活語法，可選分隔符 |\n\n## 類型定義\n\n### StylePreprocessor\n\n預處理器的通用介面．\n\n```ts\n// style/preprocessors.ts\nexport type StylePreprocessor = (\n  source: string,\n  map: RawSourceMap | undefined,\n  options: {\n    [key: string]: any;\n    additionalData?: string | ((source: string, filename: string) => string);\n    filename: string;\n  },\n  customRequire: (id: string) => any,\n) => StylePreprocessorResults;\n```\n\n### StylePreprocessorResults\n\n表示預處理器結果的類型．\n\n```ts\nexport interface StylePreprocessorResults {\n  code: string;           // 轉換後的 CSS\n  map?: object;          // Source map\n  errors: Error[];       // 錯誤列表\n  dependencies: string[]; // 依賴檔案（@import 等）\n}\n```\n\n`dependencies` 很重要．它使 Vite 等工具能夠在預處理器中通過 `@import` 匯入的檔案發生變化時觸發重新建置．\n\n## 處理流程\n\n```\nSFC 檔案 (.vue)\n    ↓\n[SFC 解析器] - 偵測 <style lang=\"scss\">\n    ↓\n[compileStyle]\n    ↓\n1. 選擇預處理器\n   processors[preprocessLang] → scss 預處理器\n    ↓\n2. 使用預處理器轉換\n   SCSS/Sass/Less/Stylus → CSS\n    ↓\n3. PostCSS 管道\n   ├── cssVarsPlugin (v-bind 處理)\n   ├── trimPlugin (空白刪除)\n   └── scopedPlugin (scoped CSS)\n    ↓\n4. 返回結果\n   { code, map, errors, dependencies }\n```\n\n## 預處理器實現\n\n### SCSS 預處理器\n\n```ts\n// style/preprocessors.ts\nconst scss: StylePreprocessor = (source, map, options, load = require) => {\n  // 動態載入 Dart Sass 函式庫\n  const nodeSass: typeof import(\"sass\") = load(\"sass\");\n  const { compileString, renderSync } = nodeSass;\n\n  // 套用 additionalData（注入公共變數等）\n  const data = getSource(source, options.filename, options.additionalData);\n\n  let css: string;\n  let dependencies: string[];\n  let sourceMap: any;\n\n  try {\n    if (compileString) {\n      // 新 API（Sass 1.55.0+）\n      const result = compileString(data, {\n        ...options,\n        url: pathToFileURL(options.filename),\n        sourceMap: !!map,\n      });\n      css = result.css;\n      dependencies = result.loadedUrls.map((url) => fileURLToPath(url));\n      sourceMap = map ? result.sourceMap! : undefined;\n    } else {\n      // 舊 API（向後相容）\n      const result = renderSync({\n        ...options,\n        data,\n        file: options.filename,\n        outFile: options.filename,\n        sourceMap: !!map,\n      });\n      css = result.css.toString();\n      dependencies = result.stats.includedFiles;\n      sourceMap = map ? JSON.parse(result.map!.toString()) : undefined;\n    }\n\n    // 合併 source map\n    if (map) {\n      return {\n        code: css,\n        errors: [],\n        dependencies,\n        map: merge(map, sourceMap!),\n      };\n    }\n    return { code: css, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n<KawaikoNote variant=\"warning\" title=\"API 相容性\">\n\nSass 有兩套 API：舊版和新版．\n`compileString` 是新 API，`renderSync` 是舊 API．\n同時支援兩者確保與任何 Sass 版本相容．\n\n</KawaikoNote>\n\n### Sass 預處理器\n\nSass 使用與 SCSS 相同的引擎，但使用基於縮排的語法．\n\n```ts\nconst sass: StylePreprocessor = (source, map, options, load) =>\n  scss(\n    source,\n    map,\n    {\n      ...options,\n      indentedSyntax: true,  // 啟用縮排語法\n    },\n    load,\n  );\n```\n\n### Less 預處理器\n\n```ts\nconst less: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeLess = load(\"less\");\n\n  let result: any;\n  let error: Error | null = null;\n\n  // Less 的 render 是非同步的，但 syncImport: true 使其同步\n  nodeLess.render(\n    getSource(source, options.filename, options.additionalData),\n    { ...options, syncImport: true },\n    (err: Error | null, output: any) => {\n      error = err;\n      result = output;\n    },\n  );\n\n  if (error) return { code: \"\", errors: [error], dependencies: [] };\n\n  // Less 通過 imports 屬性返回依賴\n  const dependencies = result.imports;\n\n  if (map) {\n    return {\n      code: result.css.toString(),\n      map: merge(map, result.map),\n      errors: [],\n      dependencies,\n    };\n  }\n\n  return {\n    code: result.css.toString(),\n    errors: [],\n    dependencies,\n  };\n};\n```\n\n### Stylus 預處理器\n\n```ts\nconst styl: StylePreprocessor = (source, map, options, load = require) => {\n  const nodeStylus = load(\"stylus\");\n\n  try {\n    const ref = nodeStylus(source, options);\n\n    // 設定 source map\n    if (map) ref.set(\"sourcemap\", { inline: false, comment: false });\n\n    const result = ref.render();\n    const dependencies = ref.deps();  // 獲取依賴\n\n    if (map) {\n      return {\n        code: result,\n        map: merge(map, ref.sourcemap),\n        errors: [],\n        dependencies,\n      };\n    }\n\n    return { code: result, errors: [], dependencies };\n  } catch (e: any) {\n    return { code: \"\", errors: [e], dependencies: [] };\n  }\n};\n```\n\n## 使用 additionalData 注入公共樣式\n\n`additionalData` 選項允許你向所有樣式檔案注入公共程式碼．\n\n```ts\nfunction getSource(\n  source: string,\n  filename: string,\n  additionalData?: string | ((source: string, filename: string) => string),\n) {\n  if (!additionalData) return source;\n\n  // 如果是函數，動態生成\n  if (isFunction(additionalData)) {\n    return additionalData(source, filename);\n  }\n\n  // 如果是字串，添加到原始碼前面\n  return additionalData + source;\n}\n```\n\n使用範例（Vite 設定）：\n\n```ts\n// vite.config.ts\nexport default defineConfig({\n  css: {\n    preprocessorOptions: {\n      scss: {\n        // 向所有 SCSS 檔案注入變數\n        additionalData: `@import \"@/styles/variables.scss\";`,\n      },\n    },\n  },\n});\n```\n\n<KawaikoNote variant=\"funny\" title=\"注入全域變數\">\n\n`additionalData` 就像「自動複製貼上到每個樣式檔案的開頭」．\n它省去了每次都要匯入變數和混入的麻煩．\n\n</KawaikoNote>\n\n## 預處理器註冊\n\n```ts\nexport type PreprocessLang = \"less\" | \"sass\" | \"scss\" | \"styl\" | \"stylus\";\n\nexport const processors: Record<PreprocessLang, StylePreprocessor> = {\n  less,\n  sass,\n  scss,\n  styl,\n  stylus: styl,  // 別名\n};\n```\n\n## 在 compileStyle 中的整合\n\n預處理器在 `compileStyle` 函數中被呼叫．\n\n```ts\n// compileStyle.ts\nexport function doCompileStyle(options: SFCAsyncStyleCompileOptions) {\n  const {\n    filename,\n    id,\n    scoped = false,\n    trim = true,\n    preprocessLang,\n    // ...\n  } = options;\n\n  // 選擇預處理器\n  const preprocessor = preprocessLang && processors[preprocessLang];\n\n  // 如果存在則執行預處理器\n  const preProcessedSource = preprocessor && preprocess(options, preprocessor);\n\n  // 獲取 source map（來自預處理器或輸入）\n  const map = preProcessedSource ? preProcessedSource.map : options.inMap;\n\n  // CSS 原始碼（轉換後的或原始的）\n  const source = preProcessedSource ? preProcessedSource.code : options.source;\n\n  // 建置 PostCSS 管道\n  const plugins = (postcssPlugins || []).slice();\n  plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));\n  if (trim) plugins.push(trimPlugin());\n  if (scoped) plugins.push(scopedPlugin(longId));\n\n  // 收集依賴\n  const dependencies = new Set(\n    preProcessedSource ? preProcessedSource.dependencies : []\n  );\n\n  // 使用 PostCSS 處理\n  const result = postcss(plugins).process(source, postCSSOptions);\n\n  return {\n    code: result.css,\n    map: result.map?.toJSON(),\n    errors: [...errors],\n    dependencies,\n  };\n}\n```\n\n## 使用範例\n\n### SCSS\n\n```vue\n<style lang=\"scss\">\n$primary: #42b883;\n$secondary: #35495e;\n\n.card {\n  background: $secondary;\n\n  .title {\n    color: $primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px rgba($secondary, 0.3);\n  }\n}\n</style>\n```\n\n### Less\n\n```vue\n<style lang=\"less\">\n@primary: #42b883;\n@secondary: #35495e;\n\n.card {\n  background: @secondary;\n\n  .title {\n    color: @primary;\n    font-size: 1.5rem;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px fade(@secondary, 30%);\n  }\n}\n</style>\n```\n\n### Stylus\n\n```vue\n<style lang=\"stylus\">\nprimary = #42b883\nsecondary = #35495e\n\n.card\n  background secondary\n\n  .title\n    color primary\n    font-size 1.5rem\n\n  &:hover\n    box-shadow 0 4px 8px rgba(secondary, 0.3)\n</style>\n```\n\n## Source Map 鏈結\n\n預處理器和 PostCSS 都會生成 source map．我們使用 `merge-source-map` 函式庫來正確地鏈結它們．\n\n```\nSCSS 原始碼\n    ↓ [SCSS → CSS]\n    ↓ Source map A\nCSS\n    ↓ [PostCSS]\n    ↓ Source map B\n最終 CSS\n    ↓\nmerge(A, B) → 最終 source map\n```\n\n這使得瀏覽器開發工具在除錯時可以顯示原始 SCSS/Less/Stylus 檔案的行號．\n\n<KawaikoNote variant=\"surprise\" title=\"除錯更輕鬆！\">\n\n有了 source map，當你在瀏覽器中想知道「這個 CSS 是從哪裡來的？」時，\n你可以看到轉換前原始 SCSS 檔案中的確切位置．\n\n</KawaikoNote>\n\n## 總結\n\nCSS 預處理器實現由以下部分組成：\n\n1. **通用介面**：用 `StylePreprocessor` 類型抽象每個預處理器\n2. **動態載入**：用 `require()` 或 `customRequire` 載入預處理器\n3. **additionalData**：注入公共樣式（變數，混入等）\n4. **依賴追蹤**：收集 `@import` 的檔案以支援熱重載\n5. **Source map 鏈結**：合併預處理器和 PostCSS 的 source map\n6. **PostCSS 整合**：將預處理器輸出傳遞給 PostCSS 管道\n\nVue/chibivue SFC 編譯器抽象了預處理器，允許使用者使用他們喜歡的 CSS 語言．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/010-plugins/020-store.md",
    "content": "# Store\n\n## 什麼是 Store？\n\n隨著應用程式變得越來越大，您通常需要在多個組件之間共享狀態．在 Vue.js 生態系統中，Pinia 提供了這個功能．\n\n在本章中，我們將實現 Pinia 的基本功能作為 chibivue-store．\n\n### 為什麼需要函式庫？\n\n如果您只是想在組件之間共享狀態，在模組作用域導出 `ref` 和 `computed` 就足夠了：\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\n\nexport const count = ref(0);\nexport const doubleCount = computed(() => count.value * 2);\nexport const increment = () => count.value++;\n```\n\n這在 CSR（客戶端渲染）中沒有問題．但是，在 SSR（伺服器端渲染）中會導致嚴重的問題．\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\n在 SSR 中，您必須注意「**Cross-Request State Pollution**（跨請求狀態污染）」．\n\n由於伺服器只初始化模組一次，上述模組作用域的狀態會**在所有請求之間共享**．\n這可能導致一個使用者的狀態洩漏給另一個使用者．\n\n</KawaikoNote>\n\n使用像 Pinia 這樣的狀態管理函式庫，只需在 setup 中呼叫 `useXxxStore()`，函式庫就會自動處理每個請求的狀態隔離．\n\n<KawaikoNote variant=\"info\" title=\"如果您使用 Nuxt\">\n\n如果您使用 Nuxt，它提供了 [useState](https://nuxt.com/docs/api/composables/use-state)，一個 SSR 友好的狀態管理組合式函式．\n對於簡單的狀態共享，`useState` 可能足夠，無需引入 Pinia．\n\n</KawaikoNote>\n\n本章涵蓋從基本的 CSR 使用到 SSR 水合．\n\n有關 SSR 的更多詳細資訊，請參閱 [SSR 章節](/zh-tw/90-web-application-essentials/020-ssr/010-create-ssr-app)．\n\n## 套件結構\n\nchibivue-store 在 `@extensions/chibivue-store` 套件中提供．\n\n```\n@extensions/chibivue-store/src/\n├── index.ts           # 導出\n├── createStore.ts     # 根 store 創建\n├── rootStore.ts       # Store 介面和符號\n└── store.ts           # defineStore 實現\n```\n\n## 類型定義\n\n### StateTree\n\n表示 store 持有的狀態的類型．\n\n```ts\n// rootStore.ts\nexport type StateTree = Record<string | number | symbol, any>;\n```\n\n### Store 介面\n\n定義根 store 的公共 API．\n\n```ts\n// rootStore.ts\nexport interface Store {\n  install: (app: App) => void;\n  use(plugin: StorePlugin): Store;\n  state: Ref<Record<string, StateTree>>;\n  _p: StorePlugin[];\n  _a: App | null;\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n- `install`: 作為 Vue 插件的安裝方法\n- `use`: 添加插件的方法\n- `state`: 保存所有 store 狀態的 ref（用於 SSR）\n- `_p`: 已安裝的插件\n- `_a`: 連結到此 store 的 App\n- `_e`: store 附加的 EffectScope\n- `_s`: 按 ID 管理已定義 store 的 Map\n\n### StoreInstance 介面\n\n定義每個 store 實例可用的方法．\n\n```ts\n// store.ts\nexport interface StoreInstance<\n  Id extends string = string,\n  S extends StateTree = StateTree,\n  G extends _GettersTree<S> = _GettersTree<S>,\n  A = Record<string, (...args: any[]) => any>,\n> {\n  $id: Id;\n  $state: S;\n  $patch: (partialState: Partial<S> | ((state: S) => void)) => void;\n  $reset: () => void;\n}\n```\n\n- `$id`: Store 識別符\n- `$state`: Store 狀態（僅 Options API 風格）\n- `$patch`: 批量狀態更新\n- `$reset`: 重置狀態為初始值（僅 Options API 風格）\n\n## 依賴注入鍵\n\n定義通過 provide/inject 共享 store 的鍵．\n\n```ts\n// rootStore.ts\nimport type { InjectionKey } from \"chibivue\";\n\nexport const storeSymbol: InjectionKey<Store> = Symbol();\n```\n\n此符號用於在整個應用程式中 provide 由 `createStore()` 創建的 store．\n\n## createStore 實現\n\n創建根 store 的函式．\n\n```ts\n// createStore.ts\nimport { effectScope, markRaw, ref } from \"chibivue\";\nimport { type Store, setActiveStore, storeSymbol } from \"./rootStore\";\n\nexport function createStore(): Store {\n  const scope = effectScope();\n\n  const state = scope.run(() => ref({}))!;\n\n  let _p: StorePlugin[] = [];\n  let toBeInstalled: StorePlugin[] = [];\n\n  const store: Store = markRaw({\n    install(app) {\n      setActiveStore(store);\n      store._a = app;\n      app.provide(storeSymbol, store);\n      toBeInstalled.forEach((plugin) => _p.push(plugin));\n      toBeInstalled = [];\n    },\n\n    use(plugin) {\n      if (!this._a) {\n        toBeInstalled.push(plugin);\n      } else {\n        _p.push(plugin);\n      }\n      return this;\n    },\n\n    _p,\n    _a: null,\n    _e: scope,\n    _s: new Map(),\n    state,\n  });\n\n  return store;\n}\n```\n\n關鍵點：\n- `effectScope()` 創建 detached scope，管理 store 的生命週期\n- `state` 是 `ref({})`，集中管理所有 store 的狀態（用於 SSR）\n- `markRaw` 使 store 對象本身不被響應式化\n- `install` 方法呼叫 `app.provide` 使 store 在整個應用程式中可用\n\n### 管理 activeStore\n\n```ts\n// rootStore.ts\nexport let activeStore: Store | undefined;\nexport const setActiveStore = (store: Store | undefined): Store | undefined =>\n  (activeStore = store);\n\nexport const getActiveStore = (): Store | undefined => {\n  const store = hasInjectionContext() && inject(storeSymbol, null);\n\n  if (__DEV__ && !store && typeof window === \"undefined\") {\n    console.warn(\n      `[chibivue-store]: Store instance not found in context. ` +\n      `This falls back to the global activeStore which exposes you to ` +\n      `cross-request state pollution on the server.`,\n    );\n  }\n\n  return store || activeStore;\n};\n```\n\n`activeStore` 用於從組件外部存取 store（例如，在其他 store 內部）．\n\n`getActiveStore` 使用 `hasInjectionContext()` 確認 injection context，在 SSR 環境中如果沒有 context 則發出警告．這可以讓開發者了解 Cross-Request State Pollution 的風險．\n\n## defineStore 實現\n\n定義單個 store 的函式．與 Pinia 一樣，它支援兩種定義風格．\n\n### Composition API 風格\n\n```ts\n// Composition API style (setup function)\nexport function defineStore<Id extends string, SS extends StateTree>(\n  id: Id,\n  setup: () => SS,\n): () => SS;\n```\n\n傳遞 `setup` 函式並使用 `ref` 和 `computed` 定義狀態．\n\n### Options API 風格\n\n```ts\n// Options API style\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(options: StoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>;\n```\n\n使用包含 `state`，`getters` 和 `actions` 的物件定義．\n\n## 使用範例\n\n### Composition API 風格\n\n```ts\n// stores/counter.ts\nimport { ref, computed } from \"chibivue\";\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", () => {\n  // State\n  const count = ref(0);\n\n  // Getters（使用 computed）\n  const doubleCount = computed(() => count.value * 2);\n\n  // Actions\n  const increment = () => {\n    count.value++;\n  };\n\n  const reset = () => {\n    count.value = 0;\n  };\n\n  return {\n    count,\n    doubleCount,\n    increment,\n    reset,\n  };\n});\n```\n\n### Options API 風格\n\n```ts\n// stores/counter.ts\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", {\n  state: () => ({\n    count: 0,\n  }),\n\n  getters: {\n    doubleCount(state) {\n      return state.count * 2;\n    },\n  },\n\n  actions: {\n    increment() {\n      this.count++;\n    },\n  },\n});\n```\n\n### 在應用程式中註冊\n\n```ts\n// main.ts\nimport { createApp } from \"chibivue\";\nimport App from \"./App.vue\";\nimport { createStore } from \"chibivue-store\";\n\nconst app = createApp(App);\napp.use(createStore());\napp.mount(\"#app\");\n```\n\n### 在組件中使用\n\n```vue\n<!-- Counter.vue -->\n<script setup>\nimport { useCounterStore } from \"../stores/counter\";\n\nconst counterStore = useCounterStore();\n</script>\n\n<template>\n  <div>\n    <p>Count: {{ counterStore.count }}</p>\n    <p>Double: {{ counterStore.doubleCount }}</p>\n    <button @click=\"counterStore.increment\">Increment</button>\n  </div>\n</template>\n```\n\n## 使用 $patch\n\n`$patch` 允許一次更新多個狀態屬性．\n\n### 物件形式\n\n```ts\nconst store = useCounterStore();\n\nstore.$patch({\n  count: 10,\n});\n```\n\n### 函式形式\n\n```ts\nconst store = useCounterStore();\n\nstore.$patch((state) => {\n  state.count += 5;\n});\n```\n\n## 使用 $reset\n\n對於使用 Options API 風格定義的 store，`$reset` 將狀態重置為初始值．\n\n```ts\nconst store = useCounterStore();\n\nstore.increment(); // count: 1\nstore.increment(); // count: 2\n\nstore.$reset(); // count: 0（回到初始值）\n```\n\n## SSR 支援\n\nchibivue-store 支援伺服器端渲染（SSR）．\n\n### store.state 屬性\n\n根 store 的 `state` 屬性允許您序列化和水合所有 store 狀態．\n\n```ts\n// Store interface\ninterface Store {\n  install: (app: App) => void;\n  state: Ref<Record<string, StateTree>>;  // 保存所有 store 的狀態\n  _e: EffectScope;\n  _s: Map<string, StoreGeneric>;\n}\n```\n\n`state` 作為 `ref({})` 創建，每個 store 的狀態保存在 `state.value[storeId]` 中．\n這樣可以：\n- SSR 序列化伺服器端狀態: `JSON.stringify(store.state.value)`\n- 客戶端水合: `store.state.value = serverState`\n\n### 伺服器端：序列化狀態\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // 重要：為每個請求創建新實例\n  // 這可以防止 Cross-Request State Pollution\n  const store = createStore();\n  const app = createApp(App);\n  app.use(store);\n\n  const html = await renderToString(app);\n\n  // 序列化 store 狀態\n  const storeState = JSON.stringify(store.state.value);\n\n  return { html, storeState };\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"每個請求新實例\">\n\n注意 `createStore()` 和 `createApp()` 是在 `render()` 函式內部呼叫的．\n**您不能在模組作用域創建它們作為單例**．\n\n```ts\n// 錯誤：在模組作用域創建是危險的\nconst store = createStore();  // 在所有請求之間共享！\nconst app = createApp(App);\n\nexport async function render() {\n  // store 和 app 在所有請求之間共享\n}\n```\n\n</KawaikoNote>\n\n### 嵌入 HTML\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.__STORE_STATE__ = ${storeState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### 客戶端：水合狀態\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\n\nconst store = createStore();\nconst app = createApp(App);\napp.use(store);\n\n// 使用伺服器狀態水合\nif (window.__STORE_STATE__) {\n  store.state.value = window.__STORE_STATE__;\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR Ready!\">\n\nchibivue-store 現在支援 SSR．\n通過將伺服器計算的狀態傳輸到客戶端，您可以在水合後保持一致的狀態．\n\n</KawaikoNote>\n\n## 未來擴展\n\n當前實現涵蓋了基本功能，但 Pinia 還有：\n\n1. **$subscribe**: 訂閱狀態變更\n2. **$onAction**: 監控 action 執行\n3. **插件系統**: 擴展 store 功能\n4. **Devtools 整合**: 狀態視覺化和時間旅行除錯\n5. **mapState / mapActions**: Options API 組件的輔助函式\n\n## 總結\n\nchibivue-store 實現包括：\n\n1. **根 Store 創建**: 使用 `createStore` 作為 Vue 插件安裝\n2. **依賴注入**: 通過 `provide/inject` 在組件樹中共享 store\n3. **兩種定義風格**: 支援 Composition API 和 Options API\n4. **Getters**: 使用 `computed` 定義派生狀態\n5. **Actions**: 可以存取 state 和 getters 的方法\n6. **$patch**: 批量狀態更新\n7. **$reset**: 重置狀態為初始值（僅 Options API）\n8. **單例模式**: 每個 store ID 只創建一個實例\n9. **SSR 支援**: 通過 `store.state` 序列化和水合狀態\n\n通過結合 Vue 的插件系統，provide/inject 和響應式系統，我們實現了全域狀態管理．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/010-plugins/030-data-fetch.md",
    "content": "# Data Fetch\n\n## 什麼是資料獲取庫？\n\n現代 Web 應用程式頻繁地從伺服器獲取資料．在 Vue.js 生態系統中，Pinia Colada 和 TanStack Query 等庫提供了這個功能．\n\n在本章中，我們將實現類似 Pinia Colada 的基本資料獲取功能，作為 chibivue-fetch．\n\n### 為什麼需要庫？\n\n簡單的資料獲取用 `fetch` 和 `ref` 似乎就足夠了：\n\n```ts\n// composables/useUser.ts\nimport { ref, onMounted } from \"chibivue\";\n\nexport function useUser(id: number) {\n  const user = ref(null);\n  const isLoading = ref(true);\n  const error = ref(null);\n\n  onMounted(async () => {\n    try {\n      const response = await fetch(`/api/users/${id}`);\n      user.value = await response.json();\n    } catch (e) {\n      error.value = e;\n    } finally {\n      isLoading.value = false;\n    }\n  });\n\n  return { user, isLoading, error };\n}\n```\n\n但是，這個實現有以下問題：\n\n1. **沒有快取**：相同的資料會被多次獲取\n2. **SSR 困難**：無法將伺服器獲取的資料傳輸到客戶端\n3. **重複請求**：相同組件多次掛載會導致重複請求\n4. **錯誤處理**：重試和重新獲取的邏輯變得複雜\n\n資料獲取庫解決了這些問題，並提供了宣告式的 API．\n\n## 套件結構\n\nchibivue-fetch 在 `@extensions/chibivue-fetch` 套件中提供．\n\n```\n@extensions/chibivue-fetch/src/\n├── index.ts           # 導出\n├── queryCache.ts      # QueryCache 實現（快取管理）\n├── useQuery.ts        # 資料獲取 hook\n├── useMutation.ts     # 資料變更 hook\n└── types.ts           # 類型定義\n```\n\n## Data State 模式\n\n與 Pinia Colada 類似，chibivue-fetch 用三種狀態表示資料狀態：\n\n```ts\ntype DataStateStatus = \"pending\" | \"error\" | \"success\";\n\ntype DataState<TData, TError> =\n  | { status: \"pending\"; data: undefined; error: null }\n  | { status: \"error\"; data: TData | undefined; error: TError }\n  | { status: \"success\"; data: TData; error: null };\n```\n\n這種狀態模型可以清楚地追蹤資料狀態．\n\n## QueryCache\n\n`QueryCache` 負責快取管理和 SSR 的狀態管理．\n\n```ts\n// queryCache.ts\nexport interface QueryCache {\n  install: (app: App) => void;\n  caches: Map<string, UseQueryEntry>;\n  options: Required<QueryCacheOptions>;\n  create: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults | null, ...) => UseQueryEntry;\n  ensure: <TData>(key: EntryKey, options: UseQueryOptionsWithDefaults) => UseQueryEntry;\n  fetch: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  refresh: <TData>(entry: UseQueryEntry) => Promise<DataState>;\n  invalidate: (entry: UseQueryEntry) => void;\n  invalidateQueries: (key?: EntryKey) => void;\n  remove: (entry: UseQueryEntry) => void;\n  track: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  untrack: (entry: UseQueryEntry, effect: EffectScope | object | null) => void;\n  setQueryData: <TData>(key: EntryKey, data: TData) => void;\n  getQueryData: <TData>(key: EntryKey) => TData | undefined;\n  prefetchQuery: <TData>(key: EntryKey, queryFn: (ctx: QueryContext) => Promise<TData>, options?: Partial<UseQueryOptionsWithDefaults>) => Promise<void>;\n  isStale: (entry: UseQueryEntry) => boolean;\n}\n```\n\n### 主要方法\n\n- `ensure`: 獲取或創建條目\n- `fetch`: 執行查詢（總是執行）\n- `refresh`: 刷新查詢（僅在 stale 或 error 時執行）\n- `invalidate`: 使條目失效（標記為 stale）\n- `invalidateQueries`: 使匹配鍵的條目失效\n- `track` / `untrack`: 追蹤組件依賴關係\n- `setQueryData` / `getQueryData`: 直接操作快取資料\n- `prefetchQuery`: 預先獲取資料並儲存到快取\n\n### createQueryCache\n\n```ts\nimport { createQueryCache } from \"chibivue-fetch\";\n\nconst queryCache = createQueryCache({\n  staleTime: 5000,       // 預設的 stale time (5秒)\n  gcTime: 300000,        // 預設的 GC time (5分鐘)\n});\n\napp.use(queryCache);\n```\n\n## useQuery\n\n`useQuery` 是用於資料獲取的組合式函式．\n\n```ts\n// useQuery.ts\nexport interface UseQueryOptions<TData = unknown, TError = Error> {\n  key: EntryKey | EntryKeyFn;\n  query: (context: QueryContext) => Promise<TData>;\n  staleTime?: number;\n  gcTime?: number;\n  refetchOnMount?: boolean | \"always\";\n  initialData?: TData | (() => TData);\n  enabled?: boolean | Ref<boolean> | ComputedRef<boolean>;\n  retry?: number | boolean;\n  retryDelay?: number;\n  meta?: QueryMeta;\n}\n\nexport interface UseQueryReturn<TData = unknown, TError = Error> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  refresh: () => Promise<DataState<TData, TError>>;\n  refetch: () => Promise<DataState<TData, TError>>;\n}\n```\n\n### 選項\n\n- `key`: 查詢的唯一鍵（作為快取鍵使用）\n- `query`: 獲取資料的非同步函式（接收 `{ signal }`）\n- `staleTime`: 資料變為「stale（過期）」的時間\n- `gcTime`: 保留未使用快取的期間（垃圾回收）\n- `enabled`: 是否啟用查詢\n- `retry`: 錯誤時的重試次數\n- `initialData`: 初始資料\n\n### 狀態\n\n- `status`: 當前狀態（`\"pending\"` | `\"error\"` | `\"success\"`）\n- `asyncStatus`: 非同步狀態（`\"idle\"` | `\"loading\"`）\n- `isPending`: 還沒有初始資料\n- `isLoading`: 初次獲取中（`isPending` 且 `asyncStatus === \"loading\"`）\n- `isSuccess`: 獲取成功\n- `isError`: 獲取失敗\n\n### refresh 和 refetch 的區別\n\n- `refresh()`: 僅在 stale 或 error 時獲取\n- `refetch()`: 總是獲取（先使快取失效）\n\n### 使用範例\n\n```ts\nimport { useQuery } from \"chibivue-fetch\";\n\nconst { data, isLoading, error, refresh } = useQuery({\n  key: [\"user\", userId],\n  query: ({ signal }) => fetch(`/api/users/${userId}`, { signal }).then((res) => res.json()),\n  staleTime: 60000, // 1分鐘內使用快取\n});\n```\n\n## useMutation\n\n`useMutation` 是用於資料變更（POST，PUT，DELETE 等）的組合式函式．\n\n```ts\n// useMutation.ts\nexport interface UseMutationOptions<TData, TError, TVariables, TContext> {\n  mutation: (variables: TVariables) => Promise<TData>;\n  onMutate?: (variables: TVariables) => TContext | Promise<TContext>;\n  onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n  onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => void | Promise<void>;\n}\n\nexport interface UseMutationReturn<TData, TError, TVariables> {\n  state: ComputedRef<DataState<TData, TError>>;\n  asyncStatus: ComputedRef<AsyncStatus>;\n  data: ComputedRef<TData | undefined>;\n  error: ComputedRef<TError | null>;\n  status: ComputedRef<DataStateStatus>;\n  isPending: ComputedRef<boolean>;\n  isLoading: ComputedRef<boolean>;\n  isSuccess: ComputedRef<boolean>;\n  isError: ComputedRef<boolean>;\n  variables: ShallowRef<TVariables | undefined>;\n  mutate: (variables: TVariables) => void;\n  mutateAsync: (variables: TVariables) => Promise<TData>;\n  reset: () => void;\n}\n```\n\n### 生命週期回調\n\n- `onMutate`: mutation 執行前呼叫（返回 context）\n- `onSuccess`: 成功時呼叫\n- `onError`: 錯誤時呼叫\n- `onSettled`: 成功或錯誤後最後呼叫\n\n### 使用範例\n\n```ts\nimport { useMutation } from \"chibivue-fetch\";\n\nconst { mutate, isLoading, isSuccess } = useMutation({\n  mutation: (newUser) => fetch(\"/api/users\", {\n    method: \"POST\",\n    body: JSON.stringify(newUser),\n  }).then((res) => res.json()),\n  onSuccess: (data) => {\n    console.log(\"User created:\", data);\n    // 使快取失效以觸發重新獲取\n    queryCache.invalidateQueries([\"users\"]);\n  },\n});\n\n// 使用\nmutate({ name: \"John\", email: \"john@example.com\" });\n```\n\n## 快取的運作方式\n\n### Entry Key\n\n`key` 作為快取鍵使用．陣列格式可以表示階層式的鍵：\n\n```ts\n// 簡單的鍵\nkey: [\"users\"]\n\n// 階層式的鍵\nkey: [\"users\", userId]\n\n// 包含物件的鍵\nkey: [\"users\", { status: \"active\", page: 1 }]\n```\n\n具有相同 `key` 的查詢共享快取．鍵會被序列化為排序後的 JSON，因此物件屬性的順序不重要．\n\n### Stale Time 和 GC Time\n\n```\n       ← staleTime →|← refetch window →|← gcTime →|\n  fetch             stale               inactive   gc\n    |-----------------|----------------------|-----|\n    data arrives      data is stale         data removed\n```\n\n- **staleTime**: 資料保持「fresh」的期間．在此期間呼叫 `refresh()` 不會重新獲取\n- **gcTime**: 保留未使用快取的期間．組件卸載後，經過此期間快取會被刪除\n\n```ts\n// 1分鐘內不重新獲取，保留快取5分鐘\nuseQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  staleTime: 60 * 1000,  // 1 minute\n  gcTime: 5 * 60 * 1000, // 5 minutes\n});\n```\n\n### 依賴關係追蹤\n\n與 Pinia Colada 類似，chibivue-fetch 追蹤每個查詢條目被哪些組件使用：\n\n```ts\n// 組件掛載時追蹤\nonMounted(() => {\n  queryCache.track(entry, currentInstance);\n});\n\n// 組件卸載時取消追蹤\nonUnmounted(() => {\n  queryCache.untrack(entry, currentInstance);\n});\n```\n\n當沒有依賴關係時，快取會在 `gcTime` 後被垃圾回收．\n\n## SSR 支援\n\nchibivue-fetch 支援伺服器端渲染（SSR）．\n\n### 伺服器端：序列化狀態\n\n```ts\n// server.ts\nimport { createApp } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport { createQueryCache, serializeQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nexport async function render() {\n  // 為每個請求創建新實例\n  const queryCache = createQueryCache();\n  const app = createApp(App);\n  app.use(queryCache);\n\n  // 在伺服器端預先獲取資料\n  await queryCache.prefetchQuery(\n    [\"users\"],\n    ({ signal }) => fetch(\"http://api/users\", { signal }).then((r) => r.json()),\n  );\n\n  const html = await renderToString(app);\n\n  // 序列化快取狀態\n  const queryState = JSON.stringify(serializeQueryCache(queryCache));\n\n  return { html, queryState };\n}\n```\n\n### 序列化格式\n\n與 Pinia Colada 類似，我們使用相對時間戳進行序列化：\n\n```ts\n// UseQueryEntryNodeSerialized = [data, error, when (relative), meta]\n{\n  '[\"users\"]': [\n    [{ id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }], // data\n    null,                                                // error\n    0,                                                   // when (relative: now - fetchTime)\n    undefined                                            // meta\n  ]\n}\n```\n\n相對時間戳可以處理伺服器和客戶端之間的時間差異．\n\n### 嵌入 HTML\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script>\n      window.__QUERY_STATE__ = ${queryState};\n    </script>\n  </head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n```\n\n### 客戶端：水合狀態\n\n```ts\n// main.ts (client)\nimport { createApp } from \"chibivue\";\nimport { createQueryCache, hydrateQueryCache } from \"chibivue-fetch\";\nimport App from \"./App.vue\";\n\nconst queryCache = createQueryCache();\nconst app = createApp(App);\napp.use(queryCache);\n\n// 使用伺服器狀態水合\nif (window.__QUERY_STATE__) {\n  hydrateQueryCache(queryCache, window.__QUERY_STATE__);\n}\n\napp.mount(\"#app\");\n```\n\n<KawaikoNote variant=\"warning\" title=\"Cross-Request State Pollution\">\n\n在 SSR 中，與 Store 類似，您必須注意 **Cross-Request State Pollution**．\n在 `render()` 函式內呼叫 `createQueryCache()`，為每個請求創建新實例．\n\n</KawaikoNote>\n\n## 實用範例\n\n### 響應式 Query Key\n\n```ts\nimport { ref, computed } from \"chibivue\";\nimport { useQuery } from \"chibivue-fetch\";\n\nconst page = ref(1);\nconst filters = ref({ status: \"active\" });\n\nconst { data, isLoading } = useQuery({\n  // 函式格式用於動態鍵\n  key: () => [\"users\", { page: page.value, ...filters.value }],\n  query: ({ signal }) => fetchUsers(page.value, filters.value, signal),\n});\n\n// 當 page 或 filters 改變時自動重新獲取\nfunction nextPage() {\n  page.value++;\n}\n```\n\n### 條件式查詢\n\n```ts\nconst userId = ref<number | null>(null);\n\nconst { data: user } = useQuery({\n  key: () => [\"user\", userId.value],\n  query: ({ signal }) => fetchUser(userId.value!, signal),\n  // userId 為 null 時不執行查詢\n  enabled: computed(() => userId.value !== null),\n});\n```\n\n### Mutation 後更新快取\n\n```ts\nconst queryCache = getActiveQueryCache();\n\nconst { mutate: createUser } = useMutation({\n  mutation: (newUser) => api.createUser(newUser),\n  onSuccess: (createdUser) => {\n    // 方法1：使快取失效並重新獲取\n    queryCache.invalidateQueries([\"users\"]);\n\n    // 方法2：直接更新快取（樂觀更新）\n    const currentUsers = queryCache.getQueryData<User[]>([\"users\"]);\n    if (currentUsers) {\n      queryCache.setQueryData([\"users\"], [...currentUsers, createdUser]);\n    }\n  },\n});\n```\n\n### 錯誤處理和重試\n\n```ts\nconst { data, error, refresh } = useQuery({\n  key: [\"users\"],\n  query: fetchUsers,\n  retry: 3,        // 最多重試3次\n  retryDelay: 1000, // 1秒後重試\n});\n\n// 在組件中\nif (error.value) {\n  // 顯示錯誤和重試按鈕\n}\n```\n\n### 使用 AbortController 取消\n\n```ts\nconst { data } = useQuery({\n  key: [\"users\"],\n  query: async ({ signal }) => {\n    const response = await fetch(\"/api/users\", { signal });\n    if (!response.ok) throw new Error(\"Failed to fetch\");\n    return response.json();\n  },\n});\n```\n\n當查詢被取消時（例如，當新請求開始時），`signal` 會被 abort．\n\n## 總結\n\nchibivue-fetch 實現包括以下要素：\n\n1. **QueryCache**：集中式快取管理和依賴關係追蹤\n2. **Data State 模式**：`pending | error | success` 的三狀態模型\n3. **useQuery**：宣告式資料獲取 API\n4. **useMutation**：資料變更管理和生命週期回調\n5. **快取策略**：通過 staleTime / gcTime 靈活控制\n6. **SSR 支援**：通過 `serializeQueryCache()` / `hydrateQueryCache()` 傳輸狀態\n7. **響應式鍵**：動態查詢鍵支援\n8. **錯誤處理**：自動重試和狀態管理\n9. **AbortController**：請求取消支援\n\n通過最小化實現 Pinia Colada 的核心功能，您可以理解資料獲取的運作方式．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/010-plugins/040-language-tools.md",
    "content": "# Language Tools\n\n## 什麼是 Language Tools？\n\nLanguage Tools 為 `.vue` 單文件組件（SFCs）提供 IDE 支援．它們啟用以下功能：\n\n- 語法高亮\n- 自動補全\n- 類型檢查\n- 轉到定義\n- 錯誤診斷\n\n在 Vue.js 生態系統中，[vuejs/language-tools](https://github.com/vuejs/language-tools) 提供此功能，它基於 [Volar.js](https://volarjs.dev/) 構建．在本章中，我們將使用 Volar.js 為 chibivue 實現最小化的語言工具．\n\n## 為什麼需要 Language Tools？\n\nTypeScript 的語言服務只能理解 `.ts` 和 `.tsx` 檔案．然而 `.vue` 檔案包含多種語言混合在一起：\n\n```vue\n<template>\n  <div>{{ message }}</div>  <!-- HTML + 表達式 -->\n</template>\n\n<script setup lang=\"ts\">\nconst message = ref('Hello')  // TypeScript\n</script>\n\n<style scoped>\ndiv { color: red; }  /* CSS */\n</style>\n```\n\nLanguage Tools 的作用是將這種複合檔案**轉換**為 TypeScript 語言服務可以理解的格式．通過這種轉換，在 `.vue` 檔案中也可以使用 TypeScript 的所有功能（類型檢查，自動補全，重構等）．\n\n## 架構概述\n\n語言工具由三個主要套件組成：\n\n```txt\n@extensions/\n├── chibivue-language-core/     # 核心語言處理\n│   ├── parseSfc.ts             # SFC 解析器\n│   ├── virtualCode.ts          # 虛擬代碼生成\n│   ├── languagePlugin.ts       # Volar.js 插件\n│   └── types.ts                # 類型定義\n├── chibivue-language-server/   # LSP 伺服器\n│   └── server.ts               # 語言伺服器協議伺服器\n└── vscode-chibivue/            # VSCode 擴充功能\n    ├── extension.ts            # 擴充功能入口點\n    ├── syntaxes/               # TextMate 語法\n    └── language-configuration.json\n```\n\n### 資料流\n\n當你在編輯器中編輯 `.vue` 檔案時，資料按以下流程處理：\n\n```txt\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                              VSCode                                         │\n│  ┌─────────────┐                                                            │\n│  │  App.vue    │  用戶編輯 .vue 檔案                                        │\n│  └──────┬──────┘                                                            │\n│         │                                                                   │\n│         ▼                                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  vscode-chibivue    │  VSCode 擴充功能檢測檔案變化                       │\n│  │  (Language Client)  │                                                    │\n│  └──────────┬──────────┘                                                    │\n└─────────────┼───────────────────────────────────────────────────────────────┘\n              │ LSP (Language Server Protocol)\n              ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│  chibivue-language-server                                                   │\n│  ┌─────────────────────┐                                                    │\n│  │  Language Server    │  接收 LSP 請求                                     │\n│  └──────────┬──────────┘                                                    │\n│             │                                                               │\n│             ▼                                                               │\n│  ┌─────────────────────┐    ┌─────────────────────┐                         │\n│  │  chibivue-language  │───▶│  Virtual Code       │                         │\n│  │  -core (Plugin)     │    │  (.vue → .ts 轉換)  │                         │\n│  └─────────────────────┘    └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  TypeScript         │                         │\n│                             │  Language Service   │  類型檢查、補全等       │\n│                             └──────────┬──────────┘                         │\n│                                        │                                    │\n│                                        ▼                                    │\n│                             ┌─────────────────────┐                         │\n│                             │  Code Mappings      │  將結果映射回原位置     │\n│                             └─────────────────────┘                         │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n## 核心概念\n\n### 虛擬代碼 (Virtual Code)\n\nLanguage Tools 的核心概念是**虛擬代碼**．通過將 `.vue` 檔案轉換為 TypeScript，可以利用 TypeScript 語言服務的所有功能．\n\n#### 轉換示例\n\n```vue\n<!-- 原始 .vue 檔案 -->\n<template>\n  <div>{{ message }}</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n</script>\n```\n\n這會被轉換為以下虛擬 TypeScript：\n\n```ts\n// 虛擬 TypeScript 代碼\nimport { ref } from 'chibivue'\n\nconst message = ref('Hello')\n\n// 用於類型檢查模板表達式的代碼\n// 實際上不會執行，但允許 TypeScript 驗證表達式類型\ndeclare const __VLS_template: () => void;\n(() => {\n  // 對應模板中的 {{ message }}\n  // TypeScript 驗證 message 是否存在且類型正確\n  const __VLS_expr0 = (message);\n})();\n```\n\n通過這種轉換：\n- 可以驗證 `message` 的類型是 `Ref<string>`\n- 如果 `message` 未定義，會報告錯誤\n- 懸停在 `message` 上時會顯示類型資訊\n\n### 代碼映射\n\n**代碼映射**將虛擬代碼中的位置連結回原始 `.vue` 檔案的位置．\n\n```txt\n原始 .vue 檔案                        虛擬 TypeScript\n─────────────────────────────────────────────────────────────\n<script setup lang=\"ts\">\nimport { ref } from 'chibivue'  ←──→  import { ref } from 'chibivue'\n                                      ↑\nconst message = ref('Hello')    ←──→  const message = ref('Hello')\n</script>                             ↑\n                                      │\n<template>                            │\n  <div>{{ message }}</div>      ←─────┼──→  const __VLS_expr0 = (message);\n</template>                           │\n                                      ↓\n                                映射將位置連結在一起\n```\n\n有了映射：\n- 虛擬代碼中的錯誤 → 在原始 `.vue` 檔案的正確位置顯示\n- 執行「轉到定義」→ 將虛擬代碼的位置轉換為原始檔案的位置\n\n## 實現\n\n### 類型定義\n\n首先，定義表示 SFC 結構的類型．\n\n```ts\n// types.ts\n\n/**\n * 表示 SFC 中的每個區塊（template、script、style）\n */\nexport interface SfcBlock {\n  /** 區塊類型（\"template\"、\"script\"、\"style\" 等） */\n  type: string;\n\n  /** 區塊內容（不包括標籤的內部內容） */\n  content: string;\n\n  /** 位置資訊（用於錯誤顯示和映射） */\n  loc: {\n    start: { line: number; column: number; offset: number };\n    end: { line: number; column: number; offset: number };\n  };\n\n  /** 區塊屬性（例如：lang=\"ts\"、scoped） */\n  attrs: Record<string, string | true>;\n\n  /** 語言指定（attrs.lang 的快捷方式） */\n  lang?: string;\n}\n\n/**\n * 表示解析後的整個 SFC\n */\nexport interface SfcDescriptor {\n  /** <template> 區塊 */\n  template: SfcBlock | null;\n\n  /** <script>（不帶 setup）區塊 */\n  script: SfcBlock | null;\n\n  /** <script setup> 區塊 */\n  scriptSetup: SfcBlock | null;\n\n  /** <style> 區塊（可以有多個） */\n  styles: SfcBlock[];\n\n  /** 自定義區塊（例如：<docs>） */\n  customBlocks: SfcBlock[];\n}\n```\n\n### SFC 解析器\n\n解析 `.vue` 檔案以生成 `SfcDescriptor`．\n\n::: tip\n在實際實現中，可以使用 chibivue 的 `@chibivue/compiler-sfc` 套件中的 `parse` 函數．這裡為了教育目的展示一個簡化的解析器．\n:::\n\n```ts\n// parseSfc.ts\nimport type { SfcBlock, SfcDescriptor } from './types';\n\n/**\n * 解析 .vue 檔案內容並返回 SfcDescriptor\n *\n * @param content - .vue 檔案的內容\n * @param fileName - 檔案名（用於錯誤訊息）\n */\nexport function parseSfc(content: string, fileName: string): SfcDescriptor {\n  const descriptor: SfcDescriptor = {\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n    customBlocks: [],\n  };\n\n  // 匹配頂級區塊的正規表達式\n  // 檢測 <tagName attrs>content</tagName> 格式\n  const blockRegex = /<(\\w+)([^>]*)>([\\s\\S]*?)<\\/\\1>/g;\n  let match: RegExpExecArray | null;\n\n  while ((match = blockRegex.exec(content)) !== null) {\n    const [fullMatch, tagName, attrsString, blockContent] = match;\n\n    // 計算區塊的起始位置\n    const startOffset = match.index + `<${tagName}${attrsString}>`.length;\n    const startPos = offsetToPosition(content, startOffset);\n\n    // 計算區塊的結束位置\n    const endOffset = startOffset + blockContent.length;\n    const endPos = offsetToPosition(content, endOffset);\n\n    // 解析屬性（例如：'lang=\"ts\" scoped' → { lang: \"ts\", scoped: true }）\n    const attrs = parseAttrs(attrsString);\n\n    const block: SfcBlock = {\n      type: tagName,\n      content: blockContent,\n      loc: {\n        start: { ...startPos, offset: startOffset },\n        end: { ...endPos, offset: endOffset },\n      },\n      attrs,\n      lang: typeof attrs.lang === 'string' ? attrs.lang : undefined,\n    };\n\n    // 按區塊類型分類\n    switch (tagName) {\n      case 'template':\n        descriptor.template = block;\n        break;\n      case 'script':\n        // 根據是否有 setup 屬性分類\n        if ('setup' in attrs) {\n          descriptor.scriptSetup = block;\n        } else {\n          descriptor.script = block;\n        }\n        break;\n      case 'style':\n        descriptor.styles.push(block);\n        break;\n      default:\n        descriptor.customBlocks.push(block);\n    }\n  }\n\n  return descriptor;\n}\n\n/**\n * 從偏移量（字元位置）計算行號和列號\n */\nfunction offsetToPosition(\n  content: string,\n  offset: number\n): { line: number; column: number } {\n  const lines = content.slice(0, offset).split('\\n');\n  return {\n    line: lines.length,\n    column: lines[lines.length - 1].length + 1,\n  };\n}\n\n/**\n * 解析屬性字串為物件\n * 示例：' lang=\"ts\" scoped' → { lang: \"ts\", scoped: true }\n */\nfunction parseAttrs(attrsString: string): Record<string, string | true> {\n  const attrs: Record<string, string | true> = {};\n  const attrRegex = /(\\w+)(?:=\"([^\"]*)\"|='([^']*)')?/g;\n  let attrMatch: RegExpExecArray | null;\n\n  while ((attrMatch = attrRegex.exec(attrsString)) !== null) {\n    const [, name, doubleQuoted, singleQuoted] = attrMatch;\n    attrs[name] = doubleQuoted ?? singleQuoted ?? true;\n  }\n\n  return attrs;\n}\n```\n\n### 虛擬代碼生成\n\n實現 Volar.js 的 `VirtualCode` 介面．這是 Language Tools 的核心．\n\n```ts\n// virtualCode.ts\nimport type {\n  CodeMapping,\n  VirtualCode,\n} from '@volar/language-core';\nimport type * as ts from 'typescript';\nimport { parseSfc } from './parseSfc';\nimport type { SfcDescriptor } from './types';\n\n/**\n * 代碼段：生成代碼的一部分及其映射資訊\n */\ntype CodeSegment = [\n  code: string,                           // 要生成的代碼\n  sourceOffsetStart?: number,             // 源檔案中的起始位置\n  sourceOffsetEnd?: number,               // 源檔案中的結束位置\n  features?: { verification?: boolean },  // 映射功能設置\n];\n\n/**\n * 將 .vue 檔案轉換為虛擬 TypeScript 代碼的類\n */\nexport class ChibivueVirtualCode implements VirtualCode {\n  id = 'root';\n  languageId = 'vue';\n  snapshot: ts.IScriptSnapshot;\n  mappings: CodeMapping[] = [];\n  embeddedCodes: VirtualCode[] = [];\n\n  private fileName: string;\n  private sfc: SfcDescriptor;\n\n  constructor(fileName: string, snapshot: ts.IScriptSnapshot) {\n    this.fileName = fileName;\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * 檔案更新時調用\n   */\n  update(snapshot: ts.IScriptSnapshot): void {\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, this.fileName);\n    this.generateVirtualCode(content);\n  }\n\n  /**\n   * 生成虛擬代碼的主處理\n   */\n  private generateVirtualCode(sourceContent: string): void {\n    const segments: CodeSegment[] = [];\n\n    // 1. 從 script/scriptSetup 生成代碼\n    this.generateScriptCode(segments);\n\n    // 2. 生成模板類型檢查代碼\n    this.generateTemplateCode(segments);\n\n    // 3. 從段構建最終代碼和映射\n    const { code, mappings } = this.buildCode(segments, sourceContent);\n\n    // 4. 註冊為嵌入代碼（TypeScript）\n    this.embeddedCodes = [\n      {\n        id: 'ts',\n        languageId: 'typescript',\n        snapshot: createScriptSnapshot(code),\n        mappings,\n        embeddedCodes: [],\n      },\n    ];\n  }\n\n  /**\n   * 從 script/scriptSetup 區塊生成 TypeScript 代碼\n   */\n  private generateScriptCode(segments: CodeSegment[]): void {\n    const { script, scriptSetup } = this.sfc;\n\n    if (scriptSetup) {\n      // 原樣輸出 <script setup> 內容\n      // 添加映射資訊（連結到源檔案位置）\n      segments.push([\n        scriptSetup.content,\n        scriptSetup.loc.start.offset,\n        scriptSetup.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    } else if (script) {\n      // 原樣輸出 <script> 內容\n      segments.push([\n        script.content,\n        script.loc.start.offset,\n        script.loc.end.offset,\n        { verification: true },\n      ]);\n      segments.push(['\\n']);\n    }\n  }\n\n  /**\n   * 生成用於類型檢查模板表達式的代碼\n   */\n  private generateTemplateCode(segments: CodeSegment[]): void {\n    const { template } = this.sfc;\n    if (!template) return;\n\n    // 添加模板類型檢查代碼\n    segments.push(['\\n// Template type-checking\\n']);\n    segments.push(['declare const __VLS_template: () => void;\\n']);\n\n    // 檢測 mustache 表達式 {{ expr }}\n    const mustacheRegex = /\\{\\{\\s*([\\s\\S]*?)\\s*\\}\\}/g;\n    let match: RegExpExecArray | null;\n    let exprIndex = 0;\n\n    while ((match = mustacheRegex.exec(template.content)) !== null) {\n      const expr = match[1];\n      // 計算表達式在源檔案中的位置\n      const exprStartInTemplate = match.index + match[0].indexOf(expr);\n      const sourceStart = template.loc.start.offset + exprStartInTemplate;\n      const sourceEnd = sourceStart + expr.length;\n\n      // 生成驗證表達式的代碼\n      // (() => { const __VLS_expr0 = (message); })();\n      segments.push([`(() => {\\n  const __VLS_expr${exprIndex} = (`]);\n      segments.push([\n        expr,\n        sourceStart,\n        sourceEnd,\n        { verification: true },\n      ]);\n      segments.push([');\\n})();\\n']);\n\n      exprIndex++;\n    }\n  }\n\n  /**\n   * 從段構建最終代碼和映射\n   */\n  private buildCode(\n    segments: CodeSegment[],\n    sourceContent: string\n  ): { code: string; mappings: CodeMapping[] } {\n    let code = '';\n    const mappings: CodeMapping[] = [];\n\n    for (const segment of segments) {\n      const [text, sourceStart, sourceEnd, features] = segment;\n\n      if (sourceStart !== undefined && sourceEnd !== undefined) {\n        // 存在映射資訊時記錄\n        mappings.push({\n          sourceOffsets: [sourceStart],\n          generatedOffsets: [code.length],\n          lengths: [sourceEnd - sourceStart],\n          data: {\n            verification: features?.verification ?? false,\n            completion: true,\n            semantic: true,\n            navigation: true,\n            structure: true,\n            format: false,\n          },\n        });\n      }\n\n      code += text;\n    }\n\n    return { code, mappings };\n  }\n}\n\n/**\n * 建立 TypeScript 腳本快照\n */\nfunction createScriptSnapshot(content: string): ts.IScriptSnapshot {\n  return {\n    getText: (start, end) => content.slice(start, end),\n    getLength: () => content.length,\n    getChangeRange: () => undefined,\n  };\n}\n```\n\n### 語言插件\n\n實現告訴 Volar.js 如何處理 `.vue` 檔案的插件．\n\n```ts\n// languagePlugin.ts\nimport type { LanguagePlugin } from '@volar/language-core';\nimport { ChibivueVirtualCode } from './virtualCode';\n\n/**\n * 為 Volar.js 建立語言插件\n *\n * 此插件負責：\n * 1. 識別 .vue 檔案\n * 2. 從 .vue 檔案生成虛擬代碼\n * 3. 向 TypeScript 語言服務提供虛擬代碼\n */\nexport function createChibivueLanguagePlugin(): LanguagePlugin<\n  string,\n  ChibivueVirtualCode\n> {\n  return {\n    /**\n     * 從檔案擴展名判斷語言 ID\n     * 對於 .vue 檔案返回 \"vue\"\n     */\n    getLanguageId(scriptId: string): string | undefined {\n      if (scriptId.endsWith('.vue')) {\n        return 'vue';\n      }\n      return undefined;\n    },\n\n    /**\n     * 建立新的虛擬代碼\n     * 首次打開檔案時調用\n     */\n    createVirtualCode(scriptId, languageId, snapshot) {\n      if (languageId === 'vue') {\n        return new ChibivueVirtualCode(scriptId, snapshot);\n      }\n      return undefined;\n    },\n\n    /**\n     * 更新現有的虛擬代碼\n     * 編輯檔案時調用\n     */\n    updateVirtualCode(_scriptId, virtualCode, snapshot) {\n      virtualCode.update(snapshot);\n      return virtualCode;\n    },\n\n    /**\n     * TypeScript 特定設置\n     */\n    typescript: {\n      /**\n       * 使 TypeScript 識別 .vue 檔案的設置\n       *\n       * - extension: 目標檔案擴展名\n       * - isMixedContent: 表示包含多種語言\n       * - scriptKind: TypeScript 的 ScriptKind\n       *   - 7 = Deferred（延遲評估，使用虛擬代碼）\n       */\n      extraFileExtensions: [\n        { extension: 'vue', isMixedContent: true, scriptKind: 7 },\n      ],\n\n      /**\n       * 從虛擬代碼獲取要傳遞給 TypeScript 的腳本\n       *\n       * @returns\n       *   - code: 嵌入的 TypeScript 代碼\n       *   - extension: \".ts\"（作為 TypeScript 處理）\n       *   - scriptKind: 3 = TS（普通 TypeScript）\n       */\n      getServiceScript(rootVirtualCode) {\n        for (const code of rootVirtualCode.embeddedCodes) {\n          if (code.id === 'ts') {\n            return {\n              code,\n              extension: '.ts',\n              scriptKind: 3, // ts.ScriptKind.TS\n            };\n          }\n        }\n        return undefined;\n      },\n    },\n  };\n}\n```\n\n### 語言伺服器\n\nLSP（語言伺服器協議）伺服器連接編輯器和語言功能．\n\n```ts\n// server.ts\nimport {\n  createConnection,\n  createServer,\n  createSimpleProjectProviderFactory,\n  loadTsdkByPath,\n} from '@volar/language-server/node';\nimport { create as createTypeScriptServices } from 'volar-service-typescript';\nimport { createChibivueLanguagePlugin } from '@chibivue/language-core';\n\n/**\n * 關於 LSP（語言伺服器協議）\n *\n * LSP 是將編輯器與語言功能分離的協議．\n *\n * ┌──────────┐                        ┌──────────────────┐\n * │  VSCode  │ ◄───── LSP 通訊 ─────► │  Language Server │\n * │  Neovim  │    (JSON-RPC over      │  （此檔案）      │\n * │  Emacs   │     stdio/IPC)         │                  │\n * └──────────┘                        └──────────────────┘\n *\n * 主要 LSP 請求：\n * - textDocument/completion: 獲取自動補全候選\n * - textDocument/hover: 獲取懸停資訊\n * - textDocument/definition: 轉到定義\n * - textDocument/references: 查找引用\n * - textDocument/rename: 重新命名符號\n * - textDocument/diagnostics: 錯誤診斷\n */\n\n// 建立 LSP 連接（通過 stdin/stdout 或 IPC 通訊）\nconst connection = createConnection();\n\n// 建立 Volar 語言伺服器\nconst server = createServer(connection);\n\n// 開始監聽連接\nconnection.listen();\n\n/**\n * 初始化請求的處理程序\n * 客戶端（編輯器）連接時調用\n */\nconnection.onInitialize((params) => {\n  // 獲取 TypeScript SDK 路徑（從客戶端傳遞）\n  const tsdk = params.initializationOptions?.typescript?.tsdk;\n\n  // 載入 TypeScript 模組\n  const ts = tsdk\n    ? loadTsdkByPath(tsdk, params.locale)\n    : require('typescript');\n\n  // 建立 chibivue 語言插件\n  const chibivuePlugin = createChibivueLanguagePlugin();\n\n  // 初始化伺服器並註冊功能\n  return server.initialize(\n    params,\n    // 專案管理設置（tsconfig.json 檢測等）\n    createSimpleProjectProviderFactory(),\n    {\n      /**\n       * 返回語言插件\n       * 負責從 .vue 檔案生成虛擬代碼\n       */\n      getLanguagePlugins() {\n        return [chibivuePlugin];\n      },\n\n      /**\n       * 返回服務插件\n       * 提供 TypeScript 語言功能（補全、診斷等）\n       */\n      getServicePlugins() {\n        return [...createTypeScriptServices(ts)];\n      },\n    }\n  );\n});\n\n/**\n * 初始化完成的處理程序\n */\nconnection.onInitialized(() => {\n  // 根據需要進行額外設置\n});\n```\n\n### VSCode 擴充功能\n\n實現將 VSCode 連接到語言伺服器的擴充功能．\n\n```ts\n// extension.ts\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport {\n  LanguageClient,\n  LanguageClientOptions,\n  ServerOptions,\n  TransportKind,\n} from 'vscode-languageclient/node';\n\nlet client: LanguageClient | undefined;\n\n/**\n * 擴充功能啟動\n * 打開 .vue 檔案時自動調用\n */\nexport async function activate(context: vscode.ExtensionContext) {\n  // 解析語言伺服器路徑\n  const serverPath = context.asAbsolutePath(\n    path.join('dist', 'server.js')\n  );\n\n  // 伺服器啟動選項\n  const serverOptions: ServerOptions = {\n    run: {\n      module: serverPath,\n      transport: TransportKind.ipc, // 通過 IPC 通訊\n    },\n    debug: {\n      module: serverPath,\n      transport: TransportKind.ipc,\n      options: { execArgv: ['--nolazy', '--inspect=6009'] },\n    },\n  };\n\n  // 客戶端選項\n  const clientOptions: LanguageClientOptions = {\n    // 處理哪些檔案\n    documentSelector: [{ scheme: 'file', language: 'vue' }],\n\n    // 初始化時傳遞給伺服器的選項\n    initializationOptions: {\n      typescript: {\n        // 使用 VSCode 內建的 TypeScript SDK\n        tsdk: path.join(\n          vscode.env.appRoot,\n          'extensions/node_modules/typescript/lib'\n        ),\n      },\n    },\n  };\n\n  // 建立 Language Client\n  client = new LanguageClient(\n    'chibivue',                    // 客戶端 ID\n    'Chibivue Language Server',   // 顯示名稱\n    serverOptions,\n    clientOptions\n  );\n\n  // 啟動語言伺服器\n  await client.start();\n\n  // 擴充功能停用時清理\n  context.subscriptions.push({\n    dispose: () => client?.stop(),\n  });\n}\n\n/**\n * 擴充功能停用\n */\nexport function deactivate(): Thenable<void> | undefined {\n  return client?.stop();\n}\n```\n\n### 語法高亮（TextMate 語法）\n\n語法高亮使用 TextMate 語法定義．這使用 VSCode 的內建功能，不涉及語言伺服器．\n\n```json\n// syntaxes/vue.tmLanguage.json\n{\n  \"name\": \"Vue\",\n  \"scopeName\": \"source.vue\",\n  \"patterns\": [\n    { \"include\": \"#template\" },\n    { \"include\": \"#script\" },\n    { \"include\": \"#style\" }\n  ],\n  \"repository\": {\n    \"template\": {\n      \"begin\": \"(<)(template)\",\n      \"end\": \"(</)(template)(>)\",\n      \"patterns\": [{ \"include\": \"text.html.basic\" }]\n    },\n    \"script\": {\n      \"begin\": \"(<)(script)\",\n      \"end\": \"(</)(script)(>)\",\n      \"patterns\": [{ \"include\": \"source.ts\" }]\n    },\n    \"style\": {\n      \"begin\": \"(<)(style)\",\n      \"end\": \"(</)(style)(>)\",\n      \"patterns\": [{ \"include\": \"source.css\" }]\n    }\n  }\n}\n```\n\n## 支援的功能\n\n| 功能           | 狀態   | 描述                           |\n| -------------- | ------ | ------------------------------ |\n| 語法高亮       | 已支援 | 通過 TextMate 語法進行顏色編碼 |\n| 自動補全       | 已支援 | 變數，函數，屬性補全           |\n| 類型檢查       | 已支援 | 通過 TypeScript 檢測類型錯誤   |\n| 轉到定義       | 已支援 | 跳轉到變數/函數定義            |\n| 錯誤診斷       | 已支援 | 顯示語法和類型錯誤             |\n| 重新命名符號   | 已支援 | 批量重新命名變數等             |\n| 懸停資訊       | 已支援 | 顯示游標位置的類型資訊         |\n\n## 總結\n\nLanguage Tools 通過將 `.vue` 檔案轉換為虛擬 TypeScript 代碼，使 SFC 中可以使用 TypeScript 的所有功能．\n\n**主要組件：**\n\n1. **SFC 解析器** - 將 `.vue` 檔案分解為 template，script 和 style 區塊\n2. **虛擬代碼生成** - 將 SFC 轉換為帶有代碼映射的 TypeScript\n3. **語言插件** - 實現 Volar.js 介面以提供虛擬代碼\n4. **語言伺服器** - 通過 LSP 與編輯器通訊\n5. **VSCode 擴充功能** - 將 VSCode 連接到語言伺服器\n\n此實現是用於教育目的的最小實現．生產環境使用的 [vuejs/language-tools](https://github.com/vuejs/language-tools) 添加了許多高級功能：\n\n- 模板指令（`v-if`，`v-for` 等）的類型檢查\n- 組件 props 類型驗證\n- `<style scoped>` 選擇器補全\n- `<template>` 中的 HTML 補全\n- 巨集支援（`defineProps`，`defineEmits`）\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/020-ssr/010-create-ssr-app.md",
    "content": "# 伺服器端渲染 (SSR)\n\n## 什麼是 SSR\n\n伺服器端渲染（SSR）是一種在伺服器上將 Vue.js 應用程式渲染為 HTML 字串並發送到客戶端的技術．這提供了以下優勢：\n\n1. **改善 SEO**：搜尋引擎爬蟲可以獲取完整的內容\n2. **更快的首次顯示**：瀏覽器無需等待 JavaScript 執行即可顯示 HTML\n3. **效能改善**：在低速裝置或網路環境下特別有效\n\n## 套件結構\n\nchibivue 的 SSR 實作在 `@chibivue/server-renderer` 套件中提供．\n\n```\npackages/server-renderer/src/\n├── index.ts\n├── renderToString.ts      # 主入口\n├── render.ts              # VNode 渲染\n└── helpers/\n    ├── ssrRenderAttrs.ts  # 屬性渲染\n    └── ssrUtils.ts        # 工具函式\n```\n\n## 型別定義\n\n### SSRBuffer\n\n在 SSR 中，我們使用名為 `SSRBuffer` 的資料結構來高效構建渲染結果．\n\n```ts\n// packages/server-renderer/src/render.ts\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\n```\n\n緩衝區可以包含：\n- **字串**：HTML 的一部分\n- **巢狀緩衝區**：子元件的結果\n- **Promise**：非同步元件的結果\n\n### SSRContext\n\n保存 SSR 期間的上下文資訊．\n\n```ts\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  __watcherHandles?: (() => void)[];\n};\n```\n\n## renderToString 實作\n\n### 主入口\n\n```ts\n// packages/server-renderer/src/renderToString.ts\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // 直接傳入 VNode 時，用包裝元件包裹\n    const vnode = input;\n    const buffer = await renderComponentVNode(\n      createVNode({ render: () => vnode }),\n      null,\n    );\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // App 實例的情況\n  const app = input;\n  const vnode = createVNode(app._component, app._props);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  // 清理 watcher\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n```\n\n### 緩衝區展開\n\n遞迴展開巢狀的緩衝區和 Promise．\n\n```ts\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  // 如果沒有非同步元素，同步處理\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    // Promise 的情況下等待解析\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    // 巢狀緩衝區遞迴處理\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n```\n\n## createBuffer 實作\n\n用於高效構建緩衝區的工廠函式．\n\n```ts\n// packages/server-renderer/src/render.ts\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        // 連續字串自動拼接優化\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      // 如果有 Promise 或非同步緩衝區，設定標誌\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n```\n\n要點：\n1. 連續字串自動拼接（記憶體效率）\n2. `appendable` 標誌追蹤是否可以拼接\n3. 如果有非同步元素，設定 `hasAsync` 標誌\n\n## 元件渲染\n\n### renderComponentVNode\n\n```ts\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  // 建立元件實例\n  const instance = (vnode.component = createComponentInstance(\n    vnode,\n    parentComponent,\n    null,\n  ));\n\n  // 執行 setup\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  // 非同步 setup 的情況下回傳 Promise\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() =>\n      renderComponentSubTree(instance),\n    );\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n```\n\n### renderComponentSubTree\n\n```ts\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    // 函式式元件\n    const root = comp(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: instance.attrs,\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    // 有 render 函式的元件\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance(prev);\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n```\n\n## VNode 渲染\n\n### renderVNode\n\n根據各種 VNode 型別進行渲染．\n\n```ts\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  // 指令的 SSR 支援\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(\n        children\n          ? `<!--${escapeHtmlComment(children as string)}-->`\n          : `<!---->`,\n      );\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      } else if (shapeFlag & ShapeFlags.TELEPORT) {\n        renderTeleportVNode(push, vnode, parentComponent);\n      }\n  }\n}\n```\n\n### renderElementVNode\n\n將 HTML 元素渲染為字串．\n\n```ts\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  // 渲染屬性\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  // void 標籤沒有閉合標籤\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      // 處理特殊屬性\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n```\n\n### renderVNodeChildren\n\n按順序渲染子元素．\n\n```ts\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n```\n\n### renderTeleportVNode\n\nTeleport 元件的 SSR 支援．\n\n```ts\nfunction renderTeleportVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const target = vnode.props && vnode.props.to;\n  const disabled = vnode.props && vnode.props.disabled;\n\n  if (!target) {\n    if (!disabled) {\n      console.warn(`Teleport is missing target prop.`);\n    }\n    return;\n  }\n\n  if (!isString(target)) {\n    console.warn(`Teleport target must be a query selector string.`);\n    return;\n  }\n\n  // disabled 的情況下內聯渲染\n  if (disabled) {\n    renderVNodeChildren(push, vnode.children as VNodeArrayChildren, parentComponent);\n  } else {\n    // enabled 的情況下插入佔位符註解\n    push(`<!--teleport start-->`);\n    push(`<!--teleport end-->`);\n  }\n}\n```\n\n## 屬性渲染\n\n### ssrRenderAttrs\n\n```ts\n// packages/server-renderer/src/helpers/ssrRenderAttrs.ts\nexport function ssrRenderAttrs(\n  props: Record<string, unknown>,\n  tag?: string,\n): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (\n      ssrIsIgnoredKey(key) ||\n      isOn(key) ||\n      (tag === \"textarea\" && key === \"value\")\n    ) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return (\n    key === \"key\" ||\n    key === \"ref\" ||\n    key === \"innerHTML\" ||\n    key === \"textContent\"\n  );\n}\n```\n\n### ssrRenderDynamicAttr\n\n渲染動態屬性．\n\n```ts\nexport function ssrRenderDynamicAttr(\n  key: string,\n  value: unknown,\n  tag?: string,\n): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n\n  // 自訂元素或 SVG 保持原樣，否則轉換\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag))\n      ? key\n      : propsToAttrMap[key] || key.toLowerCase();\n\n  // 處理布林屬性\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\"\n      ? ` ${attrKey}`\n      : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(\n      `[@chibivue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`,\n    );\n    return \"\";\n  }\n}\n```\n\n### 渲染 class 和 style\n\n```ts\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles));\n}\n\nfunction stringifyStyle(\n  styles: Record<string, string | number> | null,\n): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n```\n\n## 指令的 SSR 支援\n\n```ts\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const { dir: { getSSRProps } } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n```\n\n如果指令實作了 `getSSRProps`，其結果將合併到 props 中．\n\n## 跳脫處理\n\n防止 XSS 的 HTML 跳脫．\n\n```ts\n// packages/server-renderer/src/helpers/ssrUtils.ts\nconst escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n```\n\n## 使用範例\n\n```ts\nimport { createApp } from \"@chibivue/runtime-dom\";\nimport { renderToString } from \"@chibivue/server-renderer\";\n\nconst App = {\n  setup() {\n    return { message: \"Hello SSR!\" };\n  },\n  template: `<div>{{ message }}</div>`,\n};\n\nconst app = createApp(App);\n\n// 伺服器端渲染\nconst html = await renderToString(app);\nconsole.log(html); // <div>Hello SSR!</div>\n```\n\n## 處理流程\n\n```\nrenderToString(app)\n  ↓\ncreateVNode(app._component, app._props)\n  ↓\nrenderComponentVNode(vnode)\n  ├── createComponentInstance()\n  ├── setupComponent()\n  └── renderComponentSubTree()\n      ├── createBuffer()\n      ├── instance.render() 或 comp()\n      └── renderVNode(push, root, instance)\n          ├── Text → escapeHtml(children)\n          ├── Comment → <!--...-->\n          ├── Fragment → <!--[--> ... <!--]-->\n          ├── Element → renderElementVNode()\n          │   ├── <tag + ssrRenderAttrs(props) + >\n          │   ├── children 處理\n          │   └── </tag>\n          └── Component → renderComponentVNode()（遞迴）\n  ↓\nunrollBuffer(buffer)\n  ↓\nHTML 字串\n```\n\n## 總結\n\nchibivue 的 SSR 實作由以下元素組成：\n\n1. **SSRBuffer**：用於高效字串構建的緩衝系統（字串自動拼接，非同步支援）\n2. **renderComponentVNode**：將元件 VNode 轉換為 HTML（非同步 setup 支援）\n3. **renderVNode**：根據各種 VNode 型別進行渲染分支\n4. **renderElementVNode**：HTML 元素的字串化（void 標籤，特殊屬性支援）\n5. **ssrRenderAttrs**：屬性渲染（class/style 標準化，布林屬性，安全檢查）\n6. **跳脫處理**：防止 XSS 的 HTML 跳脫\n7. **指令支援**：透過 `getSSRProps` 在 SSR 時進行屬性注入\n\n在下一節中，我們將學習 hydration，它在客戶端「恢復」SSR 生成的 HTML．\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/010_ssr)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/020-ssr/020-hydration.md",
    "content": "# Hydration（水合）\n\n## 什麼是 Hydration？\n\n在上一章中，我們學習了如何使用 `renderToString` 將 Vue 組件渲染為 HTML 字串．但是，SSR 生成的 HTML 只是靜態標記——事件處理器和響應式都不起作用．\n\nHydration（水合）是將伺服器生成的 HTML「激活」為客戶端 Vue 應用程式的過程．\n\n<KawaikoNote variant=\"question\" title=\"為什麼叫『水合』？\">\n\n「Hydration」（水合）這個名字來自於給靜態 HTML「注入生命」的形象．\n就像乾枯的植物澆水後會變得生機勃勃一樣，我們向靜態 HTML 注入事件處理器和響應式．\n\n</KawaikoNote>\n\n## 與普通掛載的區別\n\n### 普通 `createApp`\n\n```\n1. 生成 VNode\n2. 建立新的 DOM 元素\n3. 將 DOM 插入容器\n```\n\n### `createSSRApp`（Hydration）\n\n```\n1. 生成 VNode\n2. 遍歷已存在的 DOM 元素\n3. 將 VNode 與 DOM 元素關聯\n4. 附加事件處理器\n```\n\n<KawaikoNote variant=\"funny\" title=\"Hydration 的本質\">\n\nHydration 可以理解為「不建立 DOM 的渲染」．\n由於 DOM 已經存在，我們只需要將它與 VNode 關聯起來．\n\n</KawaikoNote>\n\n## 類型定義\n\n### HydrateOptions\n\n定義 Hydration 所需的選項．\n\n```ts\n// runtime-core/hydration.ts\nexport interface HydrateOptions {\n  patchProp: (el: Element, key: string, prevValue: any, nextValue: any) => void;\n  nextSibling: (node: Node) => Node | null;\n}\n```\n\n- `patchProp`：將屬性（特別是事件處理器）附加到 DOM 元素的函數\n- `nextSibling`：遍歷 DOM 樹的函數\n\n## createHydrationRenderer 實現\n\n### 基本結構\n\n```ts\n// runtime-core/hydration.ts\nexport function createHydrationRenderer(options: HydrateOptions) {\n  const { patchProp, nextSibling } = options;\n\n  function hydrate(vnode: VNode, container: Element): void {\n    const node = container.firstChild;\n    if (node) {\n      hydrateNode(node, vnode, null);\n    }\n  }\n\n  // ... 其他函數\n\n  return { hydrate };\n}\n```\n\n`hydrate` 函數從容器的第一個子節點開始，並行遍歷 VNode 樹和 DOM 樹．\n\n### hydrateNode - 根據節點類型分支\n\n```ts\nfunction hydrateNode(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  const { type, shapeFlag } = vnode;\n\n  // 重要：將 VNode 與 DOM 元素關聯\n  vnode.el = node;\n\n  if (type === Text) {\n    // 文字節點：返回下一個兄弟節點\n    return nextSibling(node);\n  } else if (type === Comment) {\n    // 註解節點：返回下一個兄弟節點\n    return nextSibling(node);\n  } else if (type === Fragment) {\n    // Fragment：特殊處理\n    return hydrateFragment(node, vnode, parentComponent);\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    // HTML 元素：也處理子元素\n    return hydrateElement(node as Element, vnode, parentComponent);\n  }\n\n  return nextSibling(node);\n}\n```\n\n要點：\n- `vnode.el = node` 是最重要的操作．這使後續更新能夠引用正確的 DOM 元素\n- 每個函數返回「下一個要處理的 DOM 節點」\n\n### hydrateElement - HTML 元素的水合\n\n```ts\nfunction hydrateElement(\n  el: Element,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  vnode.el = el;\n\n  const { props, children, shapeFlag } = vnode;\n\n  // 附加事件處理器\n  if (props) {\n    for (const key in props) {\n      if (key.startsWith(\"on\") && typeof props[key] === \"function\") {\n        patchProp(el, key, null, props[key]);\n      }\n    }\n  }\n\n  // 水合子元素\n  if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n    hydrateChildren(el.firstChild, children as VNode[], parentComponent);\n  }\n\n  return nextSibling(el);\n}\n```\n\n<KawaikoNote variant=\"warning\" title=\"只附加事件處理器\">\n\nHydration 時我們只處理事件處理器（以 `on` 開頭的 props）．\n像 `class` 或 `style` 這樣的屬性已經包含在 SSR 的 HTML 中，所以不需要附加．\n\n</KawaikoNote>\n\n### hydrateChildren - 處理子元素\n\n```ts\nfunction hydrateChildren(\n  node: Node | null,\n  children: VNode[],\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  for (let i = 0; i < children.length; i++) {\n    const child = normalizeVNode(children[i]);\n    if (node) {\n      node = hydrateNode(node, child, parentComponent);\n    }\n  }\n  return node;\n}\n```\n\n按順序處理 VNode 子元素和 DOM 子節點．每個 `hydrateNode` 返回下一個兄弟節點，用於繼續遍歷．\n\n### hydrateFragment - Fragment 處理\n\n在 SSR 中，Fragment 被包裝在 `<!--[-->` 和 `<!--]-->` 註解節點中渲染．\n\n```ts\nfunction hydrateFragment(\n  node: Node,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null,\n): Node | null {\n  // 將開始註解（<!--[-->）設定到 el\n  vnode.el = node;\n\n  // 子元素從開始註解之後開始\n  let current = nextSibling(node);\n  const children = vnode.children as VNode[];\n\n  if (children && children.length > 0) {\n    current = hydrateChildren(current, children, parentComponent);\n  }\n\n  // 將結束註解（<!--]-->）設定到 anchor\n  vnode.anchor = current;\n  return current ? nextSibling(current) : null;\n}\n```\n\n```html\n<!-- SSR 輸出範例 -->\n<!--[-->\n<p>Item 1</p>\n<p>Item 2</p>\n<p>Item 3</p>\n<!--]-->\n```\n\n## createSSRApp 實現\n\n`createSSRApp` 與普通的 `createApp` 幾乎相同，但在掛載時執行 Hydration．\n\n```ts\n// runtime-dom/index.ts\n\n// 建立 Hydration 渲染器\nconst { hydrate: hydrateVNode } = createHydrationRenderer({\n  patchProp,\n  nextSibling: nodeOps.nextSibling,\n});\n\nexport const createSSRApp = ((...args) => {\n  const app = _createApp(...args);\n  const { mount } = app;\n\n  app.mount = (selector: string) => {\n    const container = document.querySelector(selector);\n    if (!container) return;\n\n    // 檢查容器是否有 SSR 內容\n    if (container.hasChildNodes()) {\n      // 執行 Hydration\n      const proxy = mount(container, true /* isHydrate */);\n      return proxy;\n    } else {\n      // 如果沒有 SSR 內容，普通掛載\n      mount(container);\n    }\n  };\n\n  return app;\n}) as CreateAppFunction<Element>;\n```\n\n## 處理流程\n\n```\n[伺服器端]\nrenderToString(app)\n  ↓\n<div id=\"app\">\n  <button>Count: 0</button>\n</div>\n\n[客戶端]\ncreateSSRApp(App).mount('#app')\n  ↓\ncontainer.hasChildNodes() → true\n  ↓\nhydrate(vnode, container)\n  ↓\nhydrateNode(button, vnode)\n  ├── vnode.el = button  ← 將 VNode 與 DOM 關聯\n  └── patchProp(button, 'onClick', null, handler)  ← 附加事件\n  ↓\n點擊按鈕觸發響應式\n```\n\n## 使用範例\n\n### 伺服器端\n\n```ts\n// server.ts\nimport { createApp } from '@chibivue/runtime-dom'\nimport { renderToString } from '@chibivue/server-renderer'\nimport App from './App.vue'\n\nconst app = createApp(App)\nconst html = await renderToString(app)\n\n// 將 HTML 傳送給客戶端\nres.send(`\n  <!DOCTYPE html>\n  <html>\n    <body>\n      <div id=\"app\">${html}</div>\n      <script src=\"/client.js\"></script>\n    </body>\n  </html>\n`)\n```\n\n### 客戶端\n\n```ts\n// client.ts\nimport { createSSRApp } from '@chibivue/runtime-dom'\nimport App from './App.vue'\n\n// 使用 createSSRApp（不是 createApp）\nconst app = createSSRApp(App)\napp.mount('#app')\n```\n\n### App 組件\n\n```vue\n<!-- App.vue -->\n<script setup>\nimport { ref } from '@chibivue/runtime-core'\n\nconst count = ref(0)\nconst increment = () => count.value++\n</script>\n\n<template>\n  <button @click=\"increment\">Count: {{ count }}</button>\n</template>\n```\n\n## Hydration 不匹配\n\n在 Hydration 期間，SSR 生成的 HTML 必須與客戶端生成的 VNode 匹配．如果不匹配，就會發生「Hydration 不匹配」．\n\n### 常見原因\n\n1. **日期/亂數**：`new Date()` 或 `Math.random()` 在伺服器和客戶端產生不同的值\n2. **瀏覽器特定的 API**：`window` 或 `localStorage` 在伺服器上不存在\n3. **條件分支**：伺服器和客戶端走不同的程式碼路徑\n\n### 解決方案\n\n```vue\n<script setup>\nimport { ref, onMounted } from '@chibivue/runtime-core'\n\n// 伺服器和客戶端相同的初始值\nconst clientOnly = ref(false)\n\n// 僅在客戶端更新\nonMounted(() => {\n  clientOnly.value = true\n})\n</script>\n\n<template>\n  <div v-if=\"clientOnly\">\n    This content is only shown on client\n  </div>\n</template>\n```\n\n<KawaikoNote variant=\"warning\" title=\"注意不匹配！\">\n\n當 Hydration 不匹配發生時，Vue 會發出警告，最壞的情況下 DOM 可能會損壞．\n注意確保伺服器和客戶端產生相同的輸出．\n\n</KawaikoNote>\n\n## 未來擴展\n\n當前實現是最小化的，但 Vue 本身有以下功能：\n\n1. **Hydration 不匹配偵測**：在開發模式下偵測伺服器/客戶端不一致\n2. **部分 Hydration**：只水合必要的部分（效能最佳化）\n3. **使用 PatchFlags 最佳化**：跳過靜態節點的 Hydration\n4. **非同步組件 Hydration**：與 `Suspense` 整合\n\n<KawaikoNote variant=\"surprise\" title=\"Hydration 完成！\">\n\n現在我們擁有了 SSR 的所有部分．\n通過使用 `renderToString` 進行伺服器端渲染和\n`createSSRApp` 進行 Hydration，\n我們可以實現完整的 SSR 應用程式．\n\n</KawaikoNote>\n\n## 總結\n\nHydration 實現由以下部分組成：\n\n1. **createHydrationRenderer**：建立用於 Hydration 的渲染器\n2. **hydrateNode**：根據 VNode 類型分支處理\n3. **hydrateElement**：HTML 元素和事件處理器附加\n4. **hydrateChildren**：遞迴處理子元素\n5. **hydrateFragment**：處理 Fragment（被註解節點包圍的區域）\n6. **createSSRApp**：支援 Hydration 的應用程式工廠\n\nHydration 的本質是「將 VNode 與已存在的 DOM 關聯而不重新建立」．這使得 SSR 的快速初始顯示和 SPA 的豐富互動性得以兼顧．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/020-ssr/030-compiler-ssr.md",
    "content": "# Compiler SSR\n\n## 什麼是 SSR 編譯器？\n\nSSR 編譯器（`@chibivue/compiler-ssr`）是一個將模板編譯為 SSR 優化代碼的套件．\n\n普通的客戶端編譯會輸出生成 VNode 的代碼，而 SSR 編譯器直接輸出生成 HTML 字串的代碼．這提高了伺服器端的渲染效率．\n\n<KawaikoNote variant=\"base\" title=\"客戶端與 SSR 的區別\">\n\n在客戶端：\n```js\n// 返回 VNode\nreturn _createElementVNode(\"div\", { class: \"hello\" }, \"Hello\")\n```\n\n在 SSR 中：\n```js\n// 直接 push HTML 字串\n_push(`<div class=\"hello\">Hello</div>`)\n```\n\nSSR 不經過 VNode 直接生成字串，所以效率更高！\n\n</KawaikoNote>\n\n## 套件結構\n\n```\npackages/compiler-ssr/src/\n├── index.ts                    # 主入口點\n├── runtimeHelpers.ts           # SSR 輔助函數定義\n├── ssrCodegenTransform.ts      # SSR 代碼生成轉換\n└── transforms/\n    ├── ssrTransformElement.ts   # 元素轉換\n    ├── ssrTransformComponent.ts # 組件轉換\n    ├── ssrVIf.ts               # v-if 轉換\n    └── ssrVFor.ts              # v-for 轉換\n```\n\n## 編譯流程\n\nSSR 編譯按以下步驟進行：\n\n1. **解析**：將模板轉換為 AST（使用 `@chibivue/compiler-dom` 的 `parse`）\n2. **轉換**：應用 SSR NodeTransform\n3. **SSR Codegen Transform**：將 AST 轉換為 SSR 代碼生成節點\n4. **代碼生成**：生成最終的 JavaScript 代碼\n\n```ts\n// packages/compiler-ssr/src/index.ts\nexport function compile(source: string | RootNode, options: CompilerOptions = {}): CodegenResult {\n  const ast = typeof source === \"string\" ? baseParse(source, options) : source;\n\n  transform(ast, {\n    ...options,\n    nodeTransforms: [\n      ssrTransformIf,\n      ssrTransformFor,\n      transformExpression,\n      ssrTransformElement,\n      ssrTransformComponent,\n      ...(options.nodeTransforms || []),\n    ],\n  });\n\n  // 將模板 AST 轉換為 SSR codegen AST\n  ssrCodegenTransform(ast, options);\n\n  return generate(ast, options);\n}\n```\n\n## SSR Transform Context\n\nSSR 轉換中使用的上下文．\n\n```ts\n// packages/compiler-ssr/src/ssrCodegenTransform.ts\nexport interface SSRTransformContext {\n  root: RootNode;\n  options: CompilerOptions;\n  body: (JSChildNode | IfStatement)[];\n  helpers: Set<symbol>;\n  onError: (error: Error) => void;\n  helper<T extends symbol>(name: T): T;\n  pushStringPart(part: TemplateLiteral[\"elements\"][0]): void;\n  pushStatement(statement: IfStatement | CallExpression): void;\n}\n```\n\n### pushStringPart\n\n將字串部分添加到緩衝區．連續的字串會自動合併．\n\n```ts\npushStringPart(part) {\n  if (!currentString) {\n    const currentCall = createCallExpression(`_push`);\n    body.push(currentCall);\n    currentString = createTemplateLiteral([]);\n    currentCall.arguments.push(currentString);\n  }\n  const bufferedElements = currentString.elements;\n  const lastItem = bufferedElements[bufferedElements.length - 1];\n  if (isString(part) && isString(lastItem)) {\n    // 合併連續的字串\n    bufferedElements[bufferedElements.length - 1] += part;\n  } else {\n    bufferedElements.push(part);\n  }\n}\n```\n\n### pushStatement\n\n將控制流語句（if/for）添加到緩衝區．\n\n```ts\npushStatement(statement) {\n  // 關閉當前字串緩衝區\n  currentString = null;\n  body.push(statement);\n}\n```\n\n## 元素轉換\n\n### ssrTransformElement\n\n將 HTML 元素轉換為 SSR 代碼．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformElement.ts\nexport const ssrTransformElement: NodeTransform = (node, context) => {\n  if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.ELEMENT) {\n    return;\n  }\n\n  return function ssrPostTransformElement() {\n    const openTag: TemplateLiteral[\"elements\"] = [`<${node.tag}`];\n\n    // 處理屬性\n    for (const prop of node.props) {\n      if (prop.type === NodeTypes.ATTRIBUTE) {\n        openTag.push(` ${prop.name}=\"${escapeHtml(prop.value.content)}\"`);\n      } else if (prop.type === NodeTypes.DIRECTIVE) {\n        // 處理 v-bind\n        if (prop.name === \"bind\" && prop.arg && prop.exp) {\n          // 處理 class、style 和其他屬性\n        }\n      }\n    }\n\n    node.ssrCodegenNode = createTemplateLiteral(openTag);\n  };\n};\n```\n\n#### 屬性綁定\n\n- **靜態屬性**：直接作為字串輸出\n- **v-bind:class**：使用 `ssrRenderClass` 輔助函數\n- **v-bind:style**：使用 `ssrRenderStyle` 輔助函數\n- **其他動態屬性**：使用 `ssrRenderAttr` 或 `ssrRenderDynamicAttr`\n\n### ssrProcessElement\n\n處理轉換後的元素以生成代碼．\n\n```ts\nexport function ssrProcessElement(node: PlainElementNode, context: SSRTransformContext): void {\n  // 輸出開始標籤\n  for (const element of node.ssrCodegenNode!.elements) {\n    context.pushStringPart(element);\n  }\n  context.pushStringPart(`>`);\n\n  // 處理 v-html\n  const vHtml = node.props.find(p => p.type === NodeTypes.DIRECTIVE && p.name === \"html\");\n  if (vHtml && vHtml.exp) {\n    context.pushStringPart(vHtml.exp);\n  } else if (node.children.length) {\n    processChildren(node, context);\n  }\n\n  // 結束標籤（void 元素除外）\n  if (!isVoidTag(node.tag)) {\n    context.pushStringPart(`</${node.tag}>`);\n  }\n}\n```\n\n## 組件轉換\n\n組件通過 `ssrRenderComponent` 在運行時渲染．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrTransformComponent.ts\nexport function ssrProcessComponent(\n  node: ComponentNode,\n  context: SSRTransformContext,\n  parent: { children: any[] },\n): void {\n  const vnodeCall = createCallExpression(context.helper(SSR_RENDER_VNODE), [\n    `_push`,\n    createCallExpression(context.helper(SSR_RENDER_COMPONENT), [\n      createSimpleExpression(`_component_${node.tag}`, false),\n      // props\n      node.props.length ? /* props object */ : createSimpleExpression(`null`, false),\n      // slots\n      createSimpleExpression(`null`, false),\n      // parent component\n      `_parent`,\n    ]),\n    `_parent`,\n  ]);\n\n  context.pushStatement(vnodeCall);\n}\n```\n\n## v-if 轉換\n\nv-if 被轉換為 JavaScript if 語句．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVIf.ts\nexport function ssrProcessIf(node: IfNode, context: SSRTransformContext): void {\n  const [rootBranch] = node.branches;\n  const ifStatement = createIfStatement(\n    rootBranch.condition!,\n    processIfBranch(rootBranch, context),\n  );\n  context.pushStatement(ifStatement);\n\n  let currentIf = ifStatement;\n  for (let i = 1; i < node.branches.length; i++) {\n    const branch = node.branches[i];\n    const branchBlockStatement = processIfBranch(branch, context);\n    if (branch.condition) {\n      // else-if\n      currentIf = currentIf.alternate = createIfStatement(branch.condition, branchBlockStatement);\n    } else {\n      // else\n      currentIf.alternate = branchBlockStatement;\n    }\n  }\n\n  // 如果沒有 else，輸出空註釋\n  if (!currentIf.alternate) {\n    currentIf.alternate = createBlockStatement([createCallExpression(`_push`, [\"`<!---->`\"])]);\n  }\n}\n```\n\n輸入：\n```html\n<div v-if=\"show\">Visible</div>\n<div v-else>Hidden</div>\n```\n\n輸出：\n```js\nif (show) {\n  _push(`<div>Visible</div>`)\n} else {\n  _push(`<div>Hidden</div>`)\n}\n```\n\n## v-for 轉換\n\nv-for 使用 `ssrRenderList` 輔助函數進行轉換．\n\n```ts\n// packages/compiler-ssr/src/transforms/ssrVFor.ts\nexport function ssrProcessFor(node: ForNode, context: SSRTransformContext): void {\n  const renderLoop = createFunctionExpression(createForLoopParams(node.parseResult));\n  renderLoop.body = processChildrenAsStatement(node, context);\n\n  // Fragment 標記\n  context.pushStringPart(`<!--[-->`);\n  context.pushStatement(\n    createCallExpression(context.helper(SSR_RENDER_LIST), [node.source, renderLoop]),\n  );\n  context.pushStringPart(`<!--]-->`);\n}\n```\n\n輸入：\n```html\n<div v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</div>\n```\n\n輸出：\n```js\n_push(`<!--[-->`)\n_ssrRenderList(items, (item) => {\n  _push(`<div>${_ssrInterpolate(item.name)}</div>`)\n})\n_push(`<!--]-->`)\n```\n\n## SSR 輔助函數\n\nSSR 編譯器使用以下運行時輔助函數，由 `@chibivue/server-renderer` 提供．\n\n```ts\n// packages/compiler-ssr/src/runtimeHelpers.ts\nexport const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`);\nexport const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`);\nexport const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`);\nexport const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`);\nexport const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`);\nexport const SSR_RENDER_DYNAMIC_ATTR: unique symbol = Symbol(`ssrRenderDynamicAttr`);\nexport const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`);\nexport const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(`ssrIncludeBooleanAttr`);\nexport const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`);\nexport const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`);\n```\n\n### 輔助函數的作用\n\n| 輔助函數 | 作用 |\n|---------|------|\n| `ssrInterpolate` | 轉義文本插值 |\n| `ssrRenderAttrs` | 渲染物件格式的屬性 |\n| `ssrRenderClass` | 渲染 class |\n| `ssrRenderStyle` | 渲染 style |\n| `ssrRenderList` | v-for 迭代 |\n| `ssrRenderComponent` | 創建組件 VNode |\n| `ssrRenderVNode` | 將 VNode 轉換為 HTML 字串 |\n\n## SFC 整合\n\ncompiler-sfc 支援 SSR 模式的編譯．\n\n```ts\n// packages/compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  compiler,\n  compilerOptions,\n  id,\n  scoped,\n  ssr = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = (compiler || defaultCompiler).compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n  return { code, ast, source, preamble };\n}\n```\n\n指定 `ssr: true` 會自動使用 SSR 編譯器．\n\n## 生成的代碼示例\n\n輸入模板：\n```html\n<div class=\"container\">\n  <h1>{{ title }}</h1>\n  <ul>\n    <li v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</li>\n  </ul>\n</div>\n```\n\n生成的代碼：\n```js\nimport { ssrInterpolate as _ssrInterpolate, ssrRenderList as _ssrRenderList } from 'chibivue/server-renderer'\n\nfunction ssrRender(_ctx, _push, _parent, _attrs) {\n  _push(`<div class=\"container\"><h1>${_ssrInterpolate(_ctx.title)}</h1><ul><!--[-->`)\n  _ssrRenderList(_ctx.items, (item) => {\n    _push(`<li>${_ssrInterpolate(item.name)}</li>`)\n  })\n  _push(`<!--]--></ul></div>`)\n}\n```\n\n<KawaikoNote variant=\"surprise\" title=\"SSR 編譯器的優點\">\n\n使用 SSR 編譯器可以：\n- 沒有 VNode 開銷\n- 使用模板字面量高效生成字串\n- 靜態部分直接作為字串輸出\n\n這些提高了伺服器端渲染的效能！\n\n</KawaikoNote>\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/030-builtins/010-keep-alive.md",
    "content": "# KeepAlive\n\n## 什麼是 KeepAlive\n\n`<KeepAlive>` 是一個內建組件，它可以快取並複用組件實例而不銷毀它們．通常，當組件切換時，舊組件會被卸載，狀態也會丟失．但是，透過使用 KeepAlive，您可以在切換組件時保留它們的狀態．\n\n<KawaikoNote variant=\"question\" title=\"為什麼需要 KeepAlive？\">\n\n例如，想像一個帶有標籤頁切換的介面，其中一個標籤頁正在填寫表單．\n如果您切換到另一個標籤頁再切換回來，輸入的內容消失了會很令人沮喪．\nKeepAlive 就是為了滿足這種「保留狀態」的需求！\n\n</KawaikoNote>\n\n主要用例：\n\n1. **標籤頁切換**：在表單輸入過程中切換標籤頁時保留輸入內容\n2. **路由**：在頁面導航期間保留滾動位置和輸入狀態\n3. **效能**：避免頻繁切換的組件重新渲染\n\n## 基本用法\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentTab\" />\n  </KeepAlive>\n</template>\n```\n\n## 實作概述\n\n### Props 定義\n\n```ts\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n```\n\n- **include**：要快取的組件名稱（只有包含的才會被快取）\n- **exclude**：要排除快取的組件名稱（包含的不會被快取）\n- **max**：快取的最大數量（使用 LRU 演算法刪除最舊的）\n\n### KeepAliveContext\n\nKeepAlive 組件有一個用於與渲染器互動的特殊上下文．\n\n```ts\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n```\n\n- **activate**：將快取的組件恢復顯示\n- **deactivate**：隱藏組件並快取它\n\n## 核心邏輯實作\n\n### 快取管理\n\n```ts\nconst cache: Map<any, VNode> = new Map();\nconst keys: Set<any> = new Set();\nlet current: VNode | null = null;\n\n// 用於儲存非活動組件的隱藏容器\nconst storageContainer = instance.renderer.o.createElement(\"div\");\n```\n\nKeepAlive 使用 `cache` Map 來快取組件的 VNode．`keys` Set 用於 LRU（最近最少使用）演算法的順序管理．\n\n### activate 函數\n\n從快取中恢復組件並顯示它．\n\n```ts\ninstance.activate = (vnode, container, anchor, _parentComponent) => {\n  const instance = vnode.component!;\n  // 從隱藏容器移動到實際容器\n  move(vnode, container, anchor);\n  // 套用任何 props 變化\n  patch(instance.vnode, vnode, container, anchor, parentComponent);\n  queuePostFlushCb(() => {\n    instance.isDeactivated = false;\n    // 呼叫 onActivated 鉤子\n    if (instance.a) {\n      instance.a.forEach((hook: () => void) => hook());\n    }\n  });\n};\n```\n\n關鍵點：\n1. 將 DOM 從隱藏容器移動到目標容器\n2. 透過 patch 套用 props 變化\n3. 呼叫 `onActivated` 生命週期鉤子\n\n### deactivate 函數\n\n隱藏並快取組件．\n\n```ts\ninstance.deactivate = (vnode: VNode) => {\n  // 移動到隱藏容器（DOM 不會被刪除）\n  move(vnode, storageContainer, null);\n  queuePostFlushCb(() => {\n    const instance = vnode.component!;\n    // 呼叫 onDeactivated 鉤子\n    if (instance.da) {\n      instance.da.forEach((hook: () => void) => hook());\n    }\n    instance.isDeactivated = true;\n  });\n};\n```\n\n與正常卸載不同，DOM 元素不會被刪除，只是移動到隱藏容器中．\n\n<KawaikoNote variant=\"funny\" title=\"隱藏容器技巧\">\n\n被隱藏的組件會被移動到螢幕外的「藏身處」．\n需要時，只需從「藏身處」取出即可，省去了重建的麻煩！\n\n</KawaikoNote>\n\n### render 函數\n\n這是 KeepAlive 的核心邏輯．\n\n```ts\nreturn (): VNode | undefined => {\n  if (!slots.default) {\n    return undefined;\n  }\n\n  const children = slots.default();\n  const rawVNode = children[0];\n\n  // 如果有多個子節點則不快取\n  if (children.length > 1) {\n    current = null;\n    return children as unknown as VNode;\n  }\n\n  // 如果不是組件則直接返回\n  if (\n    !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n    !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n  ) {\n    current = null;\n    return rawVNode;\n  }\n\n  let vnode = rawVNode;\n  const comp = vnode.type as any;\n  const name = getComponentName(comp);\n  const { include, exclude, max } = props;\n\n  // include/exclude 過濾\n  if (\n    (include && (!name || !matches(include, name))) ||\n    (exclude && name && matches(exclude, name))\n  ) {\n    current = vnode;\n    return rawVNode;\n  }\n\n  // 確定快取鍵\n  const key = vnode.key == null ? comp : vnode.key;\n  const cachedVNode = cache.get(key);\n\n  if (cachedVNode) {\n    // 快取命中：恢復狀態\n    vnode.el = cachedVNode.el;\n    vnode.component = cachedVNode.component;\n    vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n    // LRU：因為最近使用所以更新順序\n    keys.delete(key);\n    keys.add(key);\n  } else {\n    // 新快取條目\n    keys.add(key);\n    // 如果超過最大值則刪除最舊的\n    if (max && keys.size > parseInt(max as string, 10)) {\n      pruneCacheEntry(keys.values().next().value);\n    }\n  }\n\n  // 設定標誌讓渲染器識別 KeepAlive\n  vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  current = vnode;\n  return vnode;\n};\n```\n\n### 透過 ShapeFlags 控制\n\nKeepAlive 使用 ShapeFlags 與渲染器協調．\n\n```ts\n// 此組件應由 KeepAlive 管理\nvnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n\n// 此組件是從快取恢復的\nvnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n```\n\n渲染器檢查這些標誌，並呼叫 activate/deactivate 而不是正常的 mount/unmount．\n\n### include/exclude 匹配\n\n```ts\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n```\n\n模式支援以下格式：\n- 字串（逗號分隔）：`\"ComponentA,ComponentB\"`\n- 正規表達式：`/^Tab/`\n- 陣列：`[\"ComponentA\", /^Tab/]`\n\n### 快取清理\n\n```ts\nfunction pruneCacheEntry(key: any): void {\n  const cached = cache.get(key) as VNode;\n  // 如果當前未顯示則卸載\n  if (!current || !isSameVNodeType(cached, current)) {\n    unmount(cached);\n  } else if (current) {\n    // 如果當前正在顯示則只重設標誌\n    resetShapeFlag(current);\n  }\n  cache.delete(key);\n  keys.delete(key);\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n```\n\n## 生命週期鉤子\n\n由 KeepAlive 管理的組件可以使用額外的生命週期鉤子：\n\n- **onActivated**：當組件變為活動狀態時\n- **onDeactivated**：當組件變為非活動狀態時\n\n```ts\nimport { onActivated, onDeactivated } from 'vue'\n\nexport default {\n  setup() {\n    onActivated(() => {\n      console.log('activated!')\n    })\n    onDeactivated(() => {\n      console.log('deactivated!')\n    })\n  }\n}\n```\n\n## 使用範例\n\n### 基本用法\n\n```vue\n<template>\n  <KeepAlive>\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### 使用 include/exclude\n\n```vue\n<template>\n  <!-- 只快取 ComponentA 和 ComponentB -->\n  <KeepAlive include=\"ComponentA,ComponentB\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- 快取除 ComponentC 以外的所有組件 -->\n  <KeepAlive exclude=\"ComponentC\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n\n  <!-- 使用正規表達式匹配 -->\n  <KeepAlive :include=\"/^Tab/\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n### 使用 max\n\n```vue\n<template>\n  <!-- 最多快取 10 個組件（LRU） -->\n  <KeepAlive :max=\"10\">\n    <component :is=\"currentComponent\" />\n  </KeepAlive>\n</template>\n```\n\n## 與渲染器的整合\n\nKeepAlive 與渲染器緊密協調工作．\n\n### mountComponent 中的 KeepAlive 檢測\n\n```ts\n// packages/runtime-core/src/renderer.ts\nconst mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {\n  const instance: ComponentInternalInstance = (\n    initialVNode.component = createComponentInstance(initialVNode, parentComponent)\n  );\n\n  // 對於 KeepAlive 組件，注入渲染器\n  if (isKeepAlive(initialVNode)) {\n    (instance as KeepAliveContext).renderer = {\n      p: patch,   // patch 函數\n      m: move,    // DOM 移動函數\n      um: unmount, // 卸載函數\n      o: options,  // 宿主選項（createElement 等）\n    };\n  }\n\n  // ... 正常的掛載處理\n};\n```\n\n### processComponent 中的 KEPT_ALIVE 檢查\n\n```ts\nconst processComponent = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | null = null,\n) => {\n  if (n1 == null) {\n    // 新掛載\n    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n      // 從快取恢復：呼叫 activate\n      (parentComponent as KeepAliveContext).activate(\n        n2,\n        container,\n        anchor,\n        parentComponent as ComponentInternalInstance\n      );\n    } else {\n      // 正常掛載\n      mountComponent(n2, container, anchor, parentComponent);\n    }\n  } else {\n    updateComponent(n1, n2);\n  }\n};\n```\n\n### unmount 中的 SHOULD_KEEP_ALIVE 檢查\n\n```ts\nconst unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {\n  const { type, shapeFlag, children } = vnode;\n\n  // KeepAlive 管理下的組件會被停用而不是刪除\n  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n    (parentComponent as KeepAliveContext).deactivate(vnode);\n    return;\n  }\n\n  // 正常的卸載處理\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    unmountComponent(vnode.component!);\n  }\n  // ...\n};\n```\n\n## 處理流程\n\n```\n初次掛載：\nKeepAlive render\n  → 從插槽獲取子節點\n  → 不在快取中 → 添加到 keys\n  → 設定 COMPONENT_SHOULD_KEEP_ALIVE 標誌\n  → 返回 vnode\n      ↓\nprocessComponent\n  → 沒有 COMPONENT_KEPT_ALIVE → mountComponent\n  → isKeepAlive(vnode) → 注入渲染器\n  → 正常組件掛載\n\n從快取恢復：\nKeepAlive render\n  → 從插槽獲取子節點\n  → 快取命中 → 複用 el/component\n  → 添加 COMPONENT_KEPT_ALIVE 標誌\n  → 更新 keys 順序（LRU）\n  → 返回 vnode\n      ↓\nprocessComponent\n  → 存在 COMPONENT_KEPT_ALIVE\n  → 呼叫 parentComponent.activate()\n      ↓\nactivate\n  → 從隱藏容器移動到實際容器\n  → 透過 patch 套用 props 變化\n  → instance.isDeactivated = false\n  → 呼叫 onActivated 鉤子\n\n停用：\nunmount\n  → 存在 COMPONENT_SHOULD_KEEP_ALIVE\n  → 呼叫 parentComponent.deactivate()\n      ↓\ndeactivate\n  → 移動到隱藏容器（DOM 不會被刪除）\n  → instance.isDeactivated = true\n  → 呼叫 onDeactivated 鉤子\n  → 保留在快取中\n```\n\n<KawaikoNote variant=\"warning\" title=\"注意記憶體使用！\">\n\n被 KeepAlive 快取的組件會一直保留在記憶體中．\n快取太多會佔用記憶體，所以請使用 `max` 屬性設定上限．\n它會透過 LRU（刪除最近最少使用的項目）自動管理！\n\n</KawaikoNote>\n\n## 總結\n\nKeepAlive 的實作由以下元素組成：\n\n1. **快取系統**：使用 Map 和 Set 的 LRU 快取\n2. **隱藏容器**：保存非活動的 DOM（`createElement(\"div\")`）\n3. **activate/deactivate**：DOM 移動和生命週期管理\n4. **ShapeFlags**：與渲染器協調\n   - `COMPONENT_SHOULD_KEEP_ALIVE`：卸載時呼叫 deactivate\n   - `COMPONENT_KEPT_ALIVE`：掛載時呼叫 activate\n5. **渲染器注入**：KeepAlive 持有 patch/move/unmount 函數的參考\n6. **include/exclude/max**：靈活的快取控制\n\nKeepAlive 是一個強大的功能，可以在保留組件狀態的同時提高效能，但需要權衡記憶體使用，因此設定適當的 `max` 值很重要．\n\n<KawaikoNote variant=\"surprise\" title=\"KeepAlive 完成！\">\n\n「隱藏而不是刪除」組件是一個簡單的想法，\n但與渲染器協調和 LRU 快取的實作相當深入！\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/020_keep_alive)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/030-builtins/020-suspense.md",
    "content": "---\nwip: true\n---\n\n# Coming Soon\n\nTBD\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/030-builtins/030-transition.md",
    "content": "# Transition\n\n## 什麼是 Transition？\n\n`<Transition>` 是一個內建組件，用於在顯示或隱藏元素和組件時應用動畫．它與 CSS 過渡/動畫配合使用，實現平滑的 UI 過渡效果．\n\n<KawaikoNote variant=\"question\" title=\"為什麼需要 Transition？\">\n\n當你使用 `v-if` 切換元素的顯示/隱藏時，元素會瞬間消失或出現．\n使用 Transition，你可以輕鬆添加淡入/淡出或滑動等動畫效果！\n\n</KawaikoNote>\n\n主要用例：\n\n1. **與 v-if / v-show 組合**：條件渲染時的動畫\n2. **動態組件**：使用 `<component :is>` 時的切換動畫\n3. **路由過渡**：頁面之間的過渡效果\n\n## 基本用法\n\n```vue\n<template>\n  <button @click=\"show = !show\">Toggle</button>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n## 實作概述\n\n### Props 定義\n\n```ts\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  appearFromClass?: string;\n  appearActiveClass?: string;\n  appearToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  appear?: boolean;\n  // 生命週期鉤子\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onEnterCancelled?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n  onLeaveCancelled?: (el: Element) => void;\n  // appear 鉤子\n  onBeforeAppear?: (el: Element) => void;\n  onAppear?: (el: Element, done: () => void) => void;\n  onAfterAppear?: (el: Element) => void;\n  onAppearCancelled?: (el: Element) => void;\n}\n```\n\n### TransitionHooks 介面\n\n```ts\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n```\n\n渲染器通過此介面與 Transition 進行協調．\n\n## CSS 類別的生命週期\n\nTransition 會自動添加和移除以下 CSS 類別：\n\n### Enter（顯示元素）\n\n1. **v-enter-from**：開始狀態．在元素插入之前添加，1 幀後移除\n2. **v-enter-active**：啟用狀態．在整個過渡期間應用\n3. **v-enter-to**：結束狀態．開始後 1 幀添加，過渡結束時移除\n\n### Leave（隱藏元素）\n\n1. **v-leave-from**：開始狀態．在 leave 過渡開始時添加，1 幀後移除\n2. **v-leave-active**：啟用狀態．在整個過渡期間應用\n3. **v-leave-to**：結束狀態．開始後 1 幀添加，過渡結束時移除\n\n```\nEnter:\n┌──────────────────────────────────────────┐\n│ v-enter-from → (1 frame) → v-enter-to   │\n│ ├─────── v-enter-active ──────────────┤ │\n└──────────────────────────────────────────┘\n\nLeave:\n┌──────────────────────────────────────────┐\n│ v-leave-from → (1 frame) → v-leave-to   │\n│ ├─────── v-leave-active ──────────────┤ │\n└──────────────────────────────────────────┘\n```\n\n## 核心邏輯實作\n\n### resolveTransitionProps\n\n解析 props 並生成 TransitionHooks．\n\n```ts\nexport function resolveTransitionProps(\n  rawProps: TransitionProps\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    // ... 其他類別\n    mode = \"default\",\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  // 生成鉤子函數\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      callHook(onBeforeEnter, [el]);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    enter: makeEnterHook(false),\n    leave(el, done) {\n      // leave 邏輯\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n```\n\n### CSS 類別管理\n\n```ts\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function addTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(\n  el: Element & ElementWithTransition,\n  cls: string\n): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n```\n\n`_vtc`（Vue Transition Classes）屬性用於追蹤當前應用的過渡類別．\n\n### nextFrame\n\n為了使 CSS 過渡正常運作，我們需要等待 2 幀後再更改類別．\n\n```ts\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n```\n\n第一幀讓瀏覽器識別初始狀態，第二幀應用更改，確保過渡可靠觸發．\n\n<KawaikoNote variant=\"funny\" title=\"為什麼要等待 2 幀？\">\n\n「為什麼要呼叫兩次 `requestAnimationFrame`？」你可能會疑惑．\n第一次呼叫告訴瀏覽器「這是初始狀態」，\n第二次呼叫告訴它「這是結束狀態」，\n這樣瀏覽器才能識別過渡！\n\n</KawaikoNote>\n\n### Enter 鉤子\n\n```ts\nconst makeEnterHook = (isAppear: boolean) => {\n  return (el: Element, done: () => void) => {\n    const hook = isAppear ? onAppear : onEnter;\n    const resolve = () => finishEnter(el, isAppear, done);\n\n    callHook(hook, [el, resolve]);\n\n    nextFrame(() => {\n      removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass);\n      addTransitionClass(el, isAppear ? appearToClass : enterToClass);\n      if (!hasExplicitCallback(hook)) {\n        whenTransitionEnds(el, type, enterDuration, resolve);\n      }\n    });\n  };\n};\n```\n\n1. 呼叫使用者定義的鉤子\n2. 2 幀後，移除 from 類別並添加 to 類別\n3. 偵測過渡結束並完成處理\n\n### Leave 鉤子\n\n```ts\nleave(el, done) {\n  const resolve = () => finishLeave(el, done);\n  addTransitionClass(el, leaveFromClass);\n  // 強制重排\n  forceReflow();\n  addTransitionClass(el, leaveActiveClass);\n\n  nextFrame(() => {\n    removeTransitionClass(el, leaveFromClass);\n    addTransitionClass(el, leaveToClass);\n    if (!hasExplicitCallback(onLeave)) {\n      whenTransitionEnds(el, type, leaveDuration, resolve);\n    }\n  });\n  callHook(onLeave, [el, resolve]);\n}\n```\n\n## 偵測過渡結束\n\n### getTransitionInfo\n\n從 CSS 獲取 transition/animation 資訊．\n\n```ts\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"]\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el);\n\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  // 確定使用 transition 還是 animation\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    // animation 的情況\n  } else {\n    // 自動偵測\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0\n      ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION)\n      : null;\n  }\n\n  return { type, timeout, propCount, hasTransform };\n}\n```\n\n### whenTransitionEnds\n\n在過渡結束時執行回呼．\n\n```ts\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  // 如果提供了明確的超時，則使用它\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout);\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\"; // \"transitionend\" 或 \"animationend\"\n  let ended = 0;\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  // 超時回退\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n```\n\n要點：\n- 監聽 `transitionend` / `animationend` 事件\n- 等待與屬性數量相同的事件\n- 超時回退（以防事件未觸發的保險）\n- `_endId` 取消舊的過渡\n\n### forceReflow\n\n強制重排以確保 CSS 過渡可靠觸發．\n\n```ts\nexport function forceReflow(): number {\n  return document.body.offsetHeight;\n}\n```\n\n讀取 `offsetHeight` 強制瀏覽器重新計算樣式．\n\n<KawaikoNote variant=\"warning\" title=\"為什麼要強制重排？\">\n\n即使連續添加 CSS 類別，瀏覽器也可能為了最佳化而批次進行樣式重新計算．\n讀取 `offsetHeight` 可以強制它「立即計算！」\n\n</KawaikoNote>\n\n## Transition 組件主體\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // 在 VNode 上設定 transition 鉤子\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\nTransition 本身不渲染任何 DOM 元素；它只是在子 VNode 上附加一個 `transition` 屬性．渲染器會看到這個屬性並呼叫鉤子．\n\n## 使用範例\n\n### 基本淡入淡出\n\n```vue\n<template>\n  <Transition name=\"fade\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.5s ease;\n}\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n</style>\n```\n\n### 滑動動畫\n\n```vue\n<template>\n  <Transition name=\"slide\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<style>\n.slide-enter-active,\n.slide-leave-active {\n  transition: all 0.3s ease;\n}\n.slide-enter-from {\n  transform: translateX(-100%);\n  opacity: 0;\n}\n.slide-leave-to {\n  transform: translateX(100%);\n  opacity: 0;\n}\n</style>\n```\n\n### JavaScript 鉤子\n\n```vue\n<template>\n  <Transition\n    @before-enter=\"onBeforeEnter\"\n    @enter=\"onEnter\"\n    @after-enter=\"onAfterEnter\"\n    @leave=\"onLeave\"\n    :css=\"false\"\n  >\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n\n<script setup>\nfunction onBeforeEnter(el) {\n  el.style.opacity = 0;\n}\n\nfunction onEnter(el, done) {\n  // 使用 GSAP 等動畫庫\n  gsap.to(el, {\n    opacity: 1,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n\nfunction onLeave(el, done) {\n  gsap.to(el, {\n    opacity: 0,\n    duration: 0.5,\n    onComplete: done,\n  });\n}\n</script>\n```\n\n### 明確的 duration\n\n```vue\n<template>\n  <!-- enter: 300ms, leave: 500ms -->\n  <Transition name=\"fade\" :duration=\"{ enter: 300, leave: 500 }\">\n    <p v-if=\"show\">Hello</p>\n  </Transition>\n</template>\n```\n\n## 與 VNode 的整合\n\n### VNode.transition 屬性\n\nVNode 有一個 `transition` 屬性，用於儲存 TransitionHooks．\n\n```ts\n// packages/runtime-core/src/vnode.ts\nexport interface VNode<HostNode = any> {\n  // ... 其他屬性\n\n  // transition\n  transition: any | null;\n}\n```\n\n### 在 Transition 組件中設定\n\nTransition 組件在子 VNode 上設定 `transition` 屬性．\n\n```ts\nconst Transition = (\n  props: TransitionProps,\n  { slots }: { slots: any }\n): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    // 在 VNode 上設定 transition 鉤子\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n```\n\n### 渲染器中的處理\n\n渲染器偵測 VNode 的 `transition` 屬性，並在適當的時機呼叫鉤子：\n\n1. **插入元素時**：`beforeEnter` → DOM 插入 → `enter`\n2. **移除元素時**：`leave` → DOM 移除\n\n```ts\n// 概念性處理流程\nconst mountElement = (vnode, container, anchor) => {\n  const el = createElement(vnode.type);\n\n  // 如果有 transition，呼叫 beforeEnter\n  if (vnode.transition) {\n    vnode.transition.beforeEnter(el);\n  }\n\n  // 插入 DOM\n  insert(el, container, anchor);\n\n  // 如果有 transition，呼叫 enter\n  if (vnode.transition) {\n    vnode.transition.enter(el);\n  }\n};\n\nconst unmountElement = (vnode) => {\n  const el = vnode.el;\n\n  // 如果有 transition，呼叫 leave\n  if (vnode.transition) {\n    vnode.transition.leave(el, () => {\n      // leave 完成後從 DOM 移除\n      remove(el);\n    });\n  } else {\n    remove(el);\n  }\n};\n```\n\n## 處理流程\n\n```\nTransition 組件 render\n  ↓\n使用 resolveTransitionProps 生成 TransitionHooks\n  ↓\nchild.transition = innerProps\n  ↓\n渲染器 mountElement\n  ├── beforeEnter(el)\n  │   └── 添加 enterFromClass/enterActiveClass\n  ├── insert(el, container)\n  └── enter(el, done)\n      └── 在 nextFrame 中\n          ├── 移除 enterFromClass\n          ├── 添加 enterToClass\n          └── 使用 whenTransitionEnds 等待完成\n              └── done() 呼叫 finishEnter\n\n渲染器 unmountElement\n  └── transition.leave(el, remove)\n      ├── 添加 leaveFromClass\n      ├── forceReflow()\n      ├── 添加 leaveActiveClass\n      └── 在 nextFrame 中\n          ├── 移除 leaveFromClass\n          ├── 添加 leaveToClass\n          └── 使用 whenTransitionEnds 等待完成\n              └── remove() 從 DOM 移除\n```\n\n## 總結\n\nTransition 的實作由以下元素組成：\n\n1. **CSS 類別管理**：在 enter/leave 的每個階段添加/移除類別\n2. **nextFrame**：等待 2 幀以保證過渡觸發\n3. **forceReflow**：強制重排以重新計算樣式\n4. **whenTransitionEnds**：監聽 transitionend/animationend 事件\n5. **JavaScript 鉤子**：支援不使用 CSS 的動畫\n6. **VNode.transition**：供渲染器呼叫鉤子的屬性\n\nTransition 與 CSS 過渡/動畫密切配合，其實作基於對瀏覽器渲染管線的深入理解．\n\n<KawaikoNote variant=\"surprise\" title=\"Transition 完成！\">\n\n不僅僅是 CSS 類別操作，還有幀時序控制和重排管理——\n這個實作需要深入理解瀏覽器內部機制．\n出乎意料地深奧，不是嗎！\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/030_transition)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/040-optimizations/010-static-hoisting.md",
    "content": "# Static Hoisting（靜態提升）\n\n## 什麼是 Static Hoisting\n\nStatic Hoisting 是模板編譯時的最佳化技術之一．它偵測模板中的靜態節點（沒有響應式依賴的節點），並將它們「提升」（hoist）到渲染函數外部，從而提高重新渲染時的效能．\n\n<KawaikoNote variant=\"question\" title=\"為什麼叫提升（hoist）？\">\n\n這與 JavaScript 的「變數提升（hoisting）」是相同的概念．\n透過將渲染函數內的靜態程式碼「提升」到函數外部，\n每次呼叫函數時就不需要重新生成了！\n\n</KawaikoNote>\n\n### 最佳化效果\n\n1. **跳過 VNode 生成**：靜態節點只生成一次，之後重複使用\n2. **減少記憶體使用**：重複使用相同的 VNode 物件\n3. **跳過 patch 處理**：靜態節點可以從比較對象中排除\n\n## 最佳化前後對比\n\n### 模板\n\n```vue\n<template>\n  <div>\n    <h1>Hello World</h1>\n    <p>{{ message }}</p>\n  </div>\n</template>\n```\n\n### 無最佳化的編譯結果\n\n```js\nfunction render() {\n  return h('div', null, [\n    h('h1', null, 'Hello World'),  // 每次都生成\n    h('p', null, message.value)\n  ])\n}\n```\n\n### 套用 Static Hoisting 後\n\n```js\nconst _hoisted_1 = h('h1', null, 'Hello World')  // 在外部只生成一次\n\nfunction render() {\n  return h('div', null, [\n    _hoisted_1,  // 重複使用參照\n    h('p', null, message.value)\n  ])\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"戲劇性的前後對比！\">\n\n從每次都生成 VNode 變成只重複使用一次生成的 VNode．\n像頁首頁尾這樣不變的部分越多，效果就越顯著！\n\n</KawaikoNote>\n\n## 實作概要\n\n### ConstantTypes\n\n表示節點靜態性的列舉型別．\n\n```ts\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,    // 動態（不可提升）\n  CAN_SKIP_PATCH = 1,  // 可跳過 patch 處理\n  CAN_HOIST = 2,       // 可提升\n  CAN_STRINGIFY = 3,   // 可字串化（可進一步最佳化）\n}\n```\n\n### hoistStatic 函數\n\n在轉換階段之後呼叫，偵測並提升靜態節點．\n\n```ts\nexport function hoistStatic(root: RootNode, context: TransformContext): void {\n  walk(root, context, new Map());\n}\n```\n\n### walk 函數\n\n遞迴遍歷 AST，偵測可提升的節點．\n\n```ts\nfunction walk(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): void {\n  const { children } = node as RootNode | ElementNode;\n  if (!children) return;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (\n      child.type === NodeTypes.ELEMENT &&\n      child.tagType === 0 // 僅針對普通元素\n    ) {\n      const constantType = getConstantType(child, context, resultCache);\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          // 可提升\n          const codegenNode = child.codegenNode as VNodeCall | undefined;\n          if (codegenNode && codegenNode.type === NodeTypes.VNODE_CALL) {\n            codegenNode.isStatic = true;\n            context.hoists.push(codegenNode);\n            // 將 codegenNode 替換為提升參照\n            child.codegenNode = context.hoist(codegenNode) as VNodeCall;\n          }\n        }\n      } else {\n        // 如果是動態的，遞迴檢查子節點\n        walk(child, context, resultCache);\n      }\n    }\n  }\n}\n```\n\n要點：\n1. 僅針對普通元素（非元件）\n2. 靜態節點新增到 `context.hoists`\n3. 將原始 `codegenNode` 替換為 `_hoisted_N` 的參照\n4. 動態節點遞迴檢查子節點\n\n### getConstantType 函數\n\n判斷節點是否為靜態．\n\n```ts\nexport function getConstantType(\n  node: TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): ConstantTypes {\n  // 檢查快取\n  const cached = resultCache.get(node);\n  if (cached !== undefined) {\n    return cached;\n  }\n\n  if (node.type === NodeTypes.ELEMENT) {\n    // 元件不可提升\n    if (node.tagType !== 0) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    const element = node as PlainElementNode;\n    const codegenNode = element.codegenNode;\n\n    if (!codegenNode || codegenNode.type !== NodeTypes.VNODE_CALL) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    // 檢查是否有動態 props\n    if (codegenNode.props) {\n      const propsType = codegenNode.props.type;\n      if (propsType !== NodeTypes.JS_OBJECT_EXPRESSION) {\n        resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n\n      const properties = codegenNode.props.properties;\n      for (let i = 0; i < properties.length; i++) {\n        const { key, value } = properties[i];\n        // 如果鍵和值都不是靜態的則不可\n        if (key.type !== NodeTypes.SIMPLE_EXPRESSION || !key.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n        if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // 遞迴檢查子元素\n    if (element.children) {\n      for (let i = 0; i < element.children.length; i++) {\n        const child = element.children[i];\n        const childType = getConstantType(child, context, resultCache);\n        if (childType === ConstantTypes.NOT_CONSTANT) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // 如果有指令則不可\n    if (element.props && element.props.length > 0) {\n      for (const prop of element.props) {\n        if (prop.type === NodeTypes.DIRECTIVE) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    resultCache.set(node, ConstantTypes.CAN_HOIST);\n    return ConstantTypes.CAN_HOIST;\n  }\n\n  // 文字節點可提升\n  if (node.type === NodeTypes.TEXT) {\n    resultCache.set(node, ConstantTypes.CAN_STRINGIFY);\n    return ConstantTypes.CAN_STRINGIFY;\n  }\n\n  // 插值（{{ }}）是動態的\n  if (node.type === NodeTypes.INTERPOLATION) {\n    resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n    return ConstantTypes.NOT_CONSTANT;\n  }\n\n  resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n  return ConstantTypes.NOT_CONSTANT;\n}\n```\n\n判斷邏輯：\n1. **元件**：始終是動態的（props 和 slots 可能會變化）\n2. **動態 props**：如果有綁定（`:class`，`:style` 等）則是動態的\n3. **指令**：如果有 `v-if`，`v-for` 等則是動態的\n4. **插值表達式**：`{{ message }}` 是動態的\n5. **子元素**：只要有一個子元素是動態的，父元素也是動態的\n6. **靜態文字/屬性**：可提升\n\n### 程式碼生成\n\n```ts\nfunction genHoists(\n  hoists: (TemplateChildNode | ExpressionNode)[],\n  context: CodegenContext\n) {\n  const { push, newline } = context;\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context);\n      newline();\n    }\n  }\n}\n```\n\n將累積在 `hoists` 陣列中的節點作為常數生成在渲染函數之前．\n\n### TransformContext 的 hoist 方法\n\n```ts\nhoist(exp) {\n  context.hoists.push(exp);\n  const identifier = createSimpleExpression(\n    `_hoisted_${context.hoists.length}`,\n    false,\n  );\n  return identifier;\n}\n```\n\n將原始節點新增到 `hoists` 陣列，並回傳 `_hoisted_N` 識別符．這在渲染函數內被參照．\n\n## 可提升的範例\n\n```vue\n<template>\n  <!-- 可提升 -->\n  <div class=\"static\">Static content</div>\n  <img src=\"/logo.png\" alt=\"Logo\">\n  <p>Fixed text</p>\n\n  <!-- 不可提升 -->\n  <div :class=\"dynamicClass\">Dynamic</div>\n  <p>{{ message }}</p>\n  <div v-if=\"show\">Conditional</div>\n  <MyComponent />\n</template>\n```\n\n## 在 transform 階段的呼叫\n\n```ts\nexport function transform(root: RootNode, options: TransformOptions): void {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n\n  // 如果啟用了 hoistStatic 選項則執行\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n\n  createRootCodegen(root, context);\n  root.components = [...context.components];\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.hoists = context.hoists;\n}\n```\n\n## 選項\n\n```ts\nexport interface TransformOptions {\n  hoistStatic?: boolean;  // 啟用靜態提升\n  // ...\n}\n```\n\n## 生成程式碼範例\n\n輸入模板：\n```vue\n<template>\n  <div>\n    <header>\n      <h1>My App</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ content }}</p>\n    </main>\n  </div>\n</template>\n```\n\n生成的程式碼：\n```js\nimport { createVNode as _createVNode, toDisplayString as _toDisplayString } from 'vue'\n\n// 靜態節點提升到外部\nconst _hoisted_1 = _createVNode(\"header\", null, [\n  _createVNode(\"h1\", null, \"My App\"),\n  _createVNode(\"nav\", null, [\n    _createVNode(\"a\", { href: \"/home\" }, \"Home\"),\n    _createVNode(\"a\", { href: \"/about\" }, \"About\")\n  ])\n])\n\nfunction render(_ctx) {\n  return _createVNode(\"div\", null, [\n    _hoisted_1,  // 重複使用參照\n    _createVNode(\"main\", null, [\n      _createVNode(\"p\", null, _toDisplayString(_ctx.content))  // 動態部分\n    ])\n  ])\n}\n```\n\n## 總結\n\nStatic Hoisting 的實作由以下元素組成：\n\n1. **ConstantTypes**：表示節點靜態級別的列舉型別\n2. **getConstantType**：判斷節點是否為靜態\n3. **walk**：遍歷 AST 偵測可提升的節點\n4. **hoist**：將節點新增到提升陣列並回傳參照\n5. **genHoists**：為提升的節點生成程式碼\n\n這種最佳化在具有大量靜態內容的大型模板中顯著提高重新渲染效能．特別是對於頁首，頁尾，側邊欄等不變的 UI 部分效果顯著．\n\n<KawaikoNote variant=\"surprise\" title=\"Static Hoisting 完成！\">\n\n編譯器自動判斷「這部分不會變化」並進行最佳化．\n這是基於模板的框架獨有的優勢！\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/040_static_hoisting)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/040-optimizations/020-patch-flags.md",
    "content": "# Patch Flags\n\n## 什麼是 Patch Flags？\n\nPatch Flags 是編譯器生成的優化提示．透過為 VNode 添加標誌，執行時的差分檢測（diffing）演算法可以跳過不必要的檢查，從而提高效能．\n\n<KawaikoNote variant=\"question\" title=\"為什麼由編譯器來優化？\">\n\n撰寫模板的人類知道「這裡是動態的」「這裡是靜態的」，\n但傳統的 Virtual DOM 並不知道這些．透過讓編譯器將這些資訊傳遞給執行時，\n就可以省去不必要的比較！\n\n</KawaikoNote>\n\n### 優化的機制\n\n在普通的 Virtual DOM 差分檢測中，需要比較所有的屬性和子元素．然而，編譯器在模板解析階段就知道「哪些部分是動態的」．透過將這些資訊作為 Patch Flags 嵌入到 VNode 中，執行時只需檢查可能發生變化的部分．\n\n## PatchFlags 的定義\n\n```ts\nexport const enum PatchFlags {\n  /**\n   * 具有動態 textContent 的元素\n   */\n  TEXT = 1,\n\n  /**\n   * 具有動態 class 繫結的元素\n   */\n  CLASS = 1 << 1,  // 2\n\n  /**\n   * 具有動態 style 的元素\n   */\n  STYLE = 1 << 2,  // 4\n\n  /**\n   * 具有 class/style 以外的動態 props 的元素\n   */\n  PROPS = 1 << 3,  // 8\n\n  /**\n   * 具有動態鍵的 props 的元素\n   */\n  FULL_PROPS = 1 << 4,  // 16\n\n  /**\n   * hydration 時需要處理 props\n   */\n  NEED_HYDRATION = 1 << 5,  // 32\n\n  /**\n   * 子元素順序不變的 Fragment\n   */\n  STABLE_FRAGMENT = 1 << 6,  // 64\n\n  /**\n   * 具有 keyed 子元素的 Fragment\n   */\n  KEYED_FRAGMENT = 1 << 7,  // 128\n\n  /**\n   * 具有非 keyed 子元素的 Fragment\n   */\n  UNKEYED_FRAGMENT = 1 << 8,  // 256\n\n  /**\n   * 需要 props 以外的 patch（ref、指令等）\n   */\n  NEED_PATCH = 1 << 9,  // 512\n\n  /**\n   * 具有動態插槽的元件\n   */\n  DYNAMIC_SLOTS = 1 << 10,  // 1024\n\n  /**\n   * 開發用：根部有註解的 Fragment\n   */\n  DEV_ROOT_FRAGMENT = 1 << 11,  // 2048\n\n  // 特殊標誌（負整數）\n\n  /**\n   * 快取的靜態 VNode\n   */\n  CACHED = -1,\n\n  /**\n   * 退出優化模式的提示\n   */\n  BAIL = -2,\n}\n```\n\n## 透過位元運算進行組合\n\nPatch Flags 被設計為位元標誌，可以組合多個標誌．\n\n```ts\n// 組合標誌\nconst flag = PatchFlags.TEXT | PatchFlags.CLASS;  // 3 (0b11)\n\n// 檢查標誌\nif (flag & PatchFlags.TEXT) {\n  // TEXT 標誌已設置\n}\n\nif (flag & PatchFlags.CLASS) {\n  // CLASS 標誌已設置\n}\n```\n\n<KawaikoNote variant=\"funny\" title=\"位元運算的魔法\">\n\n`1 << 1` 是 `2`，`1 << 2` 是 `4`...只需移動位元就能建立獨立的標誌．\n用 `|`（OR）組合，用 `&`（AND）檢查．簡單但超高效！\n\n</KawaikoNote>\n\n## 從模板生成的範例\n\n### 動態文字\n\n```vue\n<template>\n  <p>{{ message }}</p>\n</template>\n```\n\n生成的程式碼：\n```js\n// patchFlag = 1 (TEXT)\ncreateVNode(\"p\", null, toDisplayString(message), 1 /* TEXT */)\n```\n\n### 動態類別\n\n```vue\n<template>\n  <div :class=\"dynamicClass\">Content</div>\n</template>\n```\n\n生成的程式碼：\n```js\n// patchFlag = 2 (CLASS)\ncreateVNode(\"div\", { class: dynamicClass }, \"Content\", 2 /* CLASS */)\n```\n\n### 多個動態屬性\n\n```vue\n<template>\n  <div :class=\"cls\" :style=\"styles\">{{ text }}</div>\n</template>\n```\n\n生成的程式碼：\n```js\n// patchFlag = 7 (TEXT | CLASS | STYLE)\ncreateVNode(\"div\",\n  { class: cls, style: styles },\n  toDisplayString(text),\n  7 /* TEXT, CLASS, STYLE */\n)\n```\n\n### 動態 props\n\n```vue\n<template>\n  <input :value=\"inputValue\" :disabled=\"isDisabled\">\n</template>\n```\n\n生成的程式碼：\n```js\n// patchFlag = 8 (PROPS)\n// dynamicProps 明確指定可能變化的 props\ncreateVNode(\"input\",\n  { value: inputValue, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]\n)\n```\n\n## 在執行時的應用\n\n### patchElement 中的優化\n\n```ts\nfunction patchElement(n1: VNode, n2: VNode) {\n  const el = n2.el = n1.el;\n  const { patchFlag, dynamicProps } = n2;\n\n  if (patchFlag > 0) {\n    // 優化路徑：根據標誌只更新必要的部分\n\n    if (patchFlag & PatchFlags.CLASS) {\n      // 只更新 class\n      if (n1.props?.class !== n2.props?.class) {\n        hostSetClass(el, n2.props?.class);\n      }\n    }\n\n    if (patchFlag & PatchFlags.STYLE) {\n      // 只更新 style\n      hostPatchStyle(el, n1.props?.style, n2.props?.style);\n    }\n\n    if (patchFlag & PatchFlags.PROPS) {\n      // 只更新指定的 props\n      for (const key of dynamicProps!) {\n        const prev = n1.props?.[key];\n        const next = n2.props?.[key];\n        if (prev !== next) {\n          hostPatchProp(el, key, prev, next);\n        }\n      }\n    }\n\n    if (patchFlag & PatchFlags.TEXT) {\n      // 只更新文字內容\n      if (n1.children !== n2.children) {\n        hostSetElementText(el, n2.children as string);\n      }\n    }\n  } else if (patchFlag === PatchFlags.FULL_PROPS) {\n    // 檢查所有 props\n    patchProps(el, n1.props, n2.props);\n  } else {\n    // 無標誌：完整 diff\n    patchProps(el, n1.props, n2.props);\n    patchChildren(n1, n2, el);\n  }\n}\n```\n\n### Fragment 的優化\n\n```ts\nfunction patchFragment(n1: VNode, n2: VNode) {\n  const { patchFlag } = n2;\n\n  if (patchFlag & PatchFlags.STABLE_FRAGMENT) {\n    // 子元素順序不變：簡單更新\n    patchBlockChildren(n1.children, n2.children);\n  } else if (patchFlag & PatchFlags.KEYED_FRAGMENT) {\n    // keyed 子元素：基於 key 的 diff\n    patchKeyedChildren(n1.children, n2.children);\n  } else {\n    // unkeyed：完整 diff\n    patchUnkeyedChildren(n1.children, n2.children);\n  }\n}\n```\n\n## 特殊標誌\n\n### CACHED (-1)\n\n表示靜態 VNode 已被快取．\n\n```js\nconst _hoisted_1 = createVNode(\"div\", null, \"Static\", -1 /* CACHED */);\n```\n\n快取的 VNode 可以跳過差分檢測．\n\n### BAIL (-2)\n\n退出優化模式的提示．當使用者使用手寫的 render 函式等編譯器優化無法應用的情況下使用．\n\n## dynamicProps\n\n與 `patchFlag` 一起使用的 `dynamicProps` 陣列明確指定哪些 props 是動態的．\n\n```ts\n// 動態 props 是 value 和 disabled\ncreateVNode(\"input\",\n  { type: \"text\", value: val, disabled: isDisabled },\n  null,\n  8 /* PROPS */,\n  [\"value\", \"disabled\"]  // dynamicProps\n)\n```\n\n這樣，由於 `type` 是靜態的，可以跳過比較，只檢查 `value` 和 `disabled`．\n\n## 與 Block Tree 的協作\n\nPatch Flags 與 Block Tree 優化協同工作．Block 擁有 `dynamicChildren` 陣列，只追蹤動態子節點．\n\n```ts\nconst block = openBlock();\nconst vnode = createBlock(\"div\", null, [\n  createVNode(\"p\", null, \"static\"),  // 不包含在 dynamicChildren 中\n  createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // 包含\n]);\n// block.dynamicChildren = [只有動態的 p]\n```\n\n更新 Block 時只需遍歷 `dynamicChildren`，因此可以跳過靜態子節點的比較．\n\n## 優化的效果\n\n### 優化前（無標誌）\n```\n比較所有 props: O(n)\n比較所有子元素: O(m)\n總計: O(n + m)\n```\n\n### 優化後（有標誌）\n```\n只比較動態 props: O(k) 其中 k << n\n只比較動態子元素: O(l) 其中 l << m\n總計: O(k + l)\n```\n\n當模板的大部分是靜態的時候，這種優化會產生顯著的效果．\n\n## 總結\n\nPatch Flags 的實作由以下要素組成：\n\n1. **位元標誌**：高效地表示多個動態元素\n2. **編譯器整合**：在模板解析時自動生成\n3. **執行時優化**：根據標誌跳過不必要的比較\n4. **dynamicProps**：明確追蹤動態 props\n5. **Block Tree 協作**：只高效更新動態子節點\n\nPatch Flags 是大幅提升 Vue 3 Virtual DOM 效能的重要優化技術．透過編譯器和執行時的協作，最大限度地發揮了基於模板的框架的優勢．\n\n<KawaikoNote variant=\"surprise\" title=\"Patch Flags 完成！\">\n\n這項技術源於「既然能解析模板，那也能提供優化提示」的想法．\n請親身體驗 JSX 所沒有的模板編譯器的優勢！\n\n</KawaikoNote>\n\n到此為止的原始碼：\n[chibivue (GitHub)](https://github.com/chibivue-land/chibivue/tree/main/book/impls/90_web_application_essentials/050_patch_flags)\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/040-optimizations/030-tree-flattening.md",
    "content": "# Tree Flattening（Block Tree）\n\n## 什麼是 Tree Flattening？\n\nTree Flattening（Block Tree）是 Vue 3 引入的高級最佳化技術．它「扁平化」並收集模板內的動態節點，允許在更新時直接更新動態節點，而不是遍歷整個樹．\n\n<KawaikoNote variant=\"question\" title=\"為什麼叫『扁平化』？\">\n\n傳統的 Virtual DOM 在更新時需要遞迴遍歷整個樹．\nTree Flattening 將動態節點「扁平化」到陣列中，\n允許直接存取而忽略巢狀結構．\n\n</KawaikoNote>\n\n## 傳統 diff 演算法的問題\n\n### 模板範例\n\n```vue\n<template>\n  <div>\n    <header>\n      <h1>Static Title</h1>\n      <nav>\n        <a href=\"/home\">Home</a>\n        <a href=\"/about\">About</a>\n      </nav>\n    </header>\n    <main>\n      <p>{{ dynamicText }}</p>  <!-- 唯一的動態部分 -->\n    </main>\n    <footer>\n      <p>Copyright 2024</p>\n    </footer>\n  </div>\n</template>\n```\n\n### 傳統方法\n\n```\n遍歷整個樹：\ndiv\n├── header (靜態)\n│   ├── h1 (靜態)\n│   └── nav (靜態)\n│       ├── a (靜態)\n│       └── a (靜態)\n├── main\n│   └── p (動態) ← 實際上只有這裡需要更新\n└── footer (靜態)\n    └── p (靜態)\n\n→ 遍歷 9 個節點來更新 1 個\n```\n\n### Tree Flattening 方法\n\n```\n只收集動態節點：\ndynamicChildren = [p]\n\n→ 直接更新 1 個節點\n```\n\n<KawaikoNote variant=\"funny\" title=\"戲劇性的效率提升！\">\n\n如果 1000 個節點中只有 10 個是動態的：\n傳統方法需要 1000 次比較，\n但 Tree Flattening 只需要 10 次比較．\n\n</KawaikoNote>\n\n## Block 概念\n\n### 什麼是 Block？\n\nBlock 是「具有穩定結構的 VNode 子樹」．在 Block 內，保證以下幾點：\n\n1. 子節點數量不變\n2. 子節點順序不變\n3. 沒有結構性指令（`v-if`，`v-for`）\n\n### 建立 Block 的元素\n\n以下元素會建立新的 Block：\n\n- 根元素\n- `v-if` 的每個分支\n- `v-for` 的每個項目\n- 組件\n\n```vue\n<template>\n  <!-- Block 1: 根 -->\n  <div>\n    <p>{{ text1 }}</p>\n\n    <!-- Block 2: v-if -->\n    <div v-if=\"show\">\n      <p>{{ text2 }}</p>\n    </div>\n\n    <!-- Block 3, 4, ...: 每個 v-for 項目 -->\n    <div v-for=\"item in items\" :key=\"item.id\">\n      <p>{{ item.text }}</p>\n    </div>\n  </div>\n</template>\n```\n\n## VNode 擴展\n\n### dynamicChildren\n\n向 VNode 添加 `dynamicChildren` 屬性來收集動態子節點．\n\n```ts\nexport interface VNode {\n  // ... 現有屬性\n\n  /**\n   * Block 內動態子節點的列表\n   * 更新時只需要遍歷這些\n   */\n  dynamicChildren: VNode[] | null;\n\n  /**\n   * patch 處理的最佳化提示\n   */\n  patchFlag: number;\n\n  /**\n   * 動態屬性名稱列表\n   */\n  dynamicProps: string[] | null;\n}\n```\n\n## openBlock 和 createBlock\n\n### Block 追蹤\n\nBlock 的建立通過 `openBlock` 和 `createBlock` 配對完成．\n\n```ts\n// 當前追蹤的 Block\nlet currentBlock: VNode[] | null = null;\n\nexport function openBlock(): void {\n  currentBlock = [];\n}\n\nexport function createBlock(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode = createVNode(type, props, children, patchFlag, dynamicProps);\n\n  // 設定收集的動態節點\n  vnode.dynamicChildren = currentBlock;\n  currentBlock = null;\n\n  return vnode;\n}\n```\n\n### 收集動態節點\n\n在 `createVNode` 中，有 patchFlag 的 VNode 會被添加到 currentBlock．\n\n```ts\nexport function createVNode(\n  type: VNodeTypes,\n  props?: VNodeProps | null,\n  children?: VNodeChildren,\n  patchFlag?: number,\n  dynamicProps?: string[]\n): VNode {\n  const vnode: VNode = {\n    type,\n    props,\n    children,\n    patchFlag: patchFlag || 0,\n    dynamicProps: dynamicProps || null,\n    dynamicChildren: null,\n    // ...\n  };\n\n  // 有 patchFlag = 動態節點\n  // 如果 currentBlock 存在則添加\n  if (patchFlag !== undefined && patchFlag > 0 && currentBlock) {\n    currentBlock.push(vnode);\n  }\n\n  return vnode;\n}\n```\n\n## 生成的程式碼\n\n### 模板\n\n```vue\n<template>\n  <div>\n    <h1>Static Title</h1>\n    <p>{{ message }}</p>\n    <span :class=\"cls\">{{ text }}</span>\n  </div>\n</template>\n```\n\n### 生成的渲染函數\n\n```js\nimport { openBlock, createBlock, createVNode, toDisplayString } from 'vue'\n\n// 靜態節點提升到外部\nconst _hoisted_1 = createVNode(\"h1\", null, \"Static Title\")\n\nfunction render(_ctx) {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // 靜態（不包含在 dynamicChildren 中）\n      createVNode(\"p\", null, toDisplayString(_ctx.message), 1 /* TEXT */),\n      createVNode(\"span\", { class: _ctx.cls }, toDisplayString(_ctx.text), 3 /* TEXT | CLASS */)\n    ])\n  )\n}\n\n// 結果 VNode：\n// {\n//   type: \"div\",\n//   children: [_hoisted_1, p, span],\n//   dynamicChildren: [p, span]  // 只有動態節點\n// }\n```\n\n## patchBlockChildren 實現\n\n更新 Block 時，只遍歷 `dynamicChildren`．\n\n```ts\nfunction patchBlockChildren(\n  oldChildren: VNode[],\n  newChildren: VNode[],\n  container: RendererElement,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  for (let i = 0; i < newChildren.length; i++) {\n    const oldVNode = oldChildren[i];\n    const newVNode = newChildren[i];\n\n    // 只 patch 動態節點\n    patch(oldVNode, newVNode, container, null, parentComponent);\n  }\n}\n```\n\n### 在 patchElement 中的使用\n\n```ts\nfunction patchElement(\n  n1: VNode,\n  n2: VNode,\n  parentComponent: ComponentInternalInstance | null\n): void {\n  const el = (n2.el = n1.el!);\n  const oldProps = n1.props || {};\n  const newProps = n2.props || {};\n\n  // 使用 patchFlag 的最佳化 patch\n  const { patchFlag, dynamicChildren } = n2;\n\n  if (patchFlag > 0) {\n    // 基於 patchFlag 只更新特定屬性\n    if (patchFlag & PatchFlags.CLASS) {\n      patchClass(el, newProps.class);\n    }\n    if (patchFlag & PatchFlags.STYLE) {\n      patchStyle(el, oldProps.style, newProps.style);\n    }\n    if (patchFlag & PatchFlags.TEXT) {\n      if (n1.children !== n2.children) {\n        el.textContent = n2.children as string;\n      }\n    }\n    // ...\n  }\n\n  // 如果有 dynamicChildren，使用最佳化路徑\n  if (dynamicChildren) {\n    patchBlockChildren(\n      n1.dynamicChildren!,\n      dynamicChildren,\n      el,\n      parentComponent\n    );\n  } else {\n    // 回退：普通子元素 patch\n    patchChildren(n1, n2, el, parentComponent);\n  }\n}\n```\n\n## 最佳化效果\n\n考慮 1000 個列表項中只更新 1 個的情況：\n\n- **完整 diff**：遍歷 1000 個以上的節點\n- **僅 Patch Flags**：遍歷 1000 個節點（屬性比較被最佳化）\n- **Tree Flattening**：只遍歷動態節點（1 個）\n\n動態節點越少，Tree Flattening 的效果就越大．\n\n## Block 失效的情況\n\n在以下情況下，Block 最佳化會被禁用（BAIL 模式）：\n\n1. **結構性指令**：`v-if`，`v-for` 建立新的 Block\n2. **動態組件**：`<component :is=\"...\">`\n3. **插槽出口**：`<slot />`\n\n```vue\n<template>\n  <div>\n    <!-- 這裡 Block 被分割 -->\n    <div v-if=\"show\">\n      <p>{{ a }}</p>  <!-- Block A 的 dynamicChildren -->\n    </div>\n    <div v-else>\n      <p>{{ b }}</p>  <!-- Block B 的 dynamicChildren -->\n    </div>\n  </div>\n</template>\n```\n\n## 與 Static Hoisting 的整合\n\nTree Flattening 與 Static Hoisting 結合時效果最佳．\n\n```ts\n// 靜態節點被提升，不包含在 dynamicChildren 中\nconst _hoisted_1 = createVNode(\"header\", null, [\n  createVNode(\"h1\", null, \"Title\"),\n  createVNode(\"nav\", null, [/* ... */])\n]);\n\nfunction render() {\n  return (\n    openBlock(),\n    createBlock(\"div\", null, [\n      _hoisted_1,  // 靜態：跳過\n      createVNode(\"p\", null, toDisplayString(msg), 1 /* TEXT */)  // 動態：追蹤\n    ])\n  )\n}\n```\n\n1. **Static Hoisting**：將靜態節點提升到函數外部（跳過 VNode 生成）\n2. **Tree Flattening**：只收集動態節點（限制 diff 目標）\n3. **Patch Flags**：只更新動態屬性（最佳化屬性比較）\n\n## 處理流程\n\n```\n[編譯時]\n模板解析\n  ↓\n偵測靜態節點 → Static Hoisting\n  ↓\n偵測動態節點 → 添加 Patch Flags\n  ↓\n識別 Block 邊界 → 插入 openBlock/createBlock\n\n[執行時]\nopenBlock() → currentBlock = []\n  ↓\ncreateVNode (靜態) → 不添加到 currentBlock\n  ↓\ncreateVNode (動態) → currentBlock.push(vnode)\n  ↓\ncreateBlock() → vnode.dynamicChildren = currentBlock\n\n[更新時]\npatchElement(n1, n2)\n  ↓\nn2.dynamicChildren 存在？\n  ↓ 是\npatchBlockChildren(n1.dynamicChildren, n2.dynamicChildren)\n  ↓\n只 patch 動態節點\n```\n\n## 總結\n\nTree Flattening（Block Tree）實現由以下部分組成：\n\n1. **dynamicChildren**：收集動態子節點的陣列\n2. **openBlock / createBlock**：Block 建立和追蹤\n3. **patchBlockChildren**：只 patch 動態節點\n4. **Block 邊界管理**：用 `v-if`，`v-for` 等建立新 Block\n\n這個最佳化使 Vue 3 即使在大規模應用程式中也能實現快速更新．與 Static Hoisting 和 Patch Flags 結合時，可以實現基於模板的框架特有的最佳化．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/050-vapor/010-introduction.md",
    "content": "# Vapor 模式\n\n## 什麼是 Vapor 模式？\n\nVapor 模式是 Vue.js 的一種新的編譯策略，通過直接進行 DOM 操作而不使用虛擬 DOM 來提高性能．\n\n在傳統的 Vue.js 中，當組件的狀態發生變化時，會重新生成虛擬 DOM，進行差異檢測（diffing），然後更新實際的 DOM．在 Vapor 模式中，消除了虛擬 DOM 的開銷，當響應式值發生變化時，只直接執行必要的 DOM 操作．\n\n## 詳細資源\n\n關於 Vapor 模式的詳細解釋，請參閱以下倉庫：\n\n**[reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor)**\n\n該倉庫提供了 Vue.js Vapor 模式內部實現的深入解釋．\n\n## chibivue 中的 Vapor 實現\n\nchibivue 在 `runtime-vapor` 套件中提供了最小的 Vapor 實現．\n讓我們看一個簡單的實現來理解基本概念．\n\n### 基本思想\n\nVapor 模式的核心包括兩點：\n\n1. **將模板直接轉換為 DOM**：生成實際的 DOM 元素而不是虛擬 DOM 節點\n2. **將響應式值的變化直接反映到 DOM**：無需差異檢測，只更新變化的部分\n\n### template 函數\n\n首先，讓我們看看從 HTML 字符串創建 DOM 元素的 `template` 函數：\n\n```ts\nexport type VaporNode = Element & { __is_vapor: true };\n\nexport const template = (tmp: string): VaporNode => {\n  const container = document.createElement(\"div\");\n  container.innerHTML = tmp;\n  const el = container.firstElementChild as VaporNode;\n  el.__is_vapor = true;\n  return el;\n};\n```\n\n這個函數接收一個 HTML 字符串並返回一個實際的 DOM 元素．它直接操作 DOM，而不經過虛擬 DOM．\n\n### setText 函數\n\n`setText` 函數用於更新文本內容：\n\n```ts\nexport const setText = (\n  target: Element,\n  format: string,\n  ...values: any[]\n): void => {\n  const fmt = (): string => {\n    let text = format;\n    for (let i = 0; i < values.length; i++) {\n      text = text.replace(\"{}\", values[i]);\n    }\n    return text;\n  };\n\n  if (!target) return;\n\n  if (!values.length) {\n    target.textContent = fmt();\n    return;\n  }\n\n  if (!format && values.length) {\n    target.textContent = values.join(\"\");\n    return;\n  }\n\n  target.textContent = fmt();\n};\n```\n\n當響應式值發生變化時，會調用這個函數，直接更新 DOM 的文本內容．\n\n### on 函數\n\n`on` 函數用於註冊事件監聽器：\n\n```ts\nexport const on = (\n  element: Element,\n  event: string,\n  callback: () => void\n): void => {\n  element.addEventListener(event, callback);\n};\n```\n\n### Vapor 組件\n\nVapor 模式中的組件與普通的 Vue 組件形式不同：\n\n```ts\nexport type VaporComponent = (self: VaporComponentInternalInstance) => VaporNode;\n\nexport interface VaporComponentInternalInstance {\n  __is_vapor: true;\n  uid: number;\n  type: VaporComponent;\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null;\n  appContext: AppContext;\n  provides: Data;\n  isMounted: boolean;\n  // 生命週期鉤子\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  // ...\n}\n```\n\nVapor 組件是一個接收實例並返回 VaporNode（實際的 DOM 元素）的函數．\n\n### 編譯結果對比\n\n傳統的基於虛擬 DOM 的編譯結果：\n\n```ts\n// 輸入: <div>{{ count }}</div>\n// 虛擬 DOM 輸出\nfunction render(_ctx) {\n  return h(\"div\", null, _ctx.count);\n}\n```\n\nVapor 模式的編譯結果：\n\n```ts\n// 輸入: <div>{{ count }}</div>\n// Vapor 輸出\nconst t0 = template(\"<div></div>\");\n\nfunction render(_ctx) {\n  const el = t0();\n  effect(() => {\n    setText(el, _ctx.count);\n  });\n  return el;\n}\n```\n\n在 Vapor 模式中：\n- 模板被預先生成為 DOM 元素（使用 `template` 函數）\n- 響應式值的更新在 `effect` 內直接操作 DOM\n- 沒有虛擬 DOM 生成和差異檢測的成本\n\n## 總結\n\nVapor 模式是一種通過消除虛擬 DOM 開銷來提高性能的新方法．chibivue 的 `runtime-vapor` 套件提供了這個概念的最小實現．\n\n有關更詳細的實現和 Vue.js 官方的 Vapor 模式，請參閱 [reading-vuejs-core-vapor](https://github.com/ubugeeei/reading-vuejs-core-vapor)．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/050-vapor/020-vapor-compiler.md",
    "content": "# Vapor 編譯器\n\n在上一節中，我們了解了構成 Vapor 模式基礎的運行時函數（`template`，`setText`，`on`）．\n在本節中，讓我們實現一個編譯器，它可以從模板自動生成使用這些函數的代碼．\n\n## Vapor 編譯器的目標\n\nVapor 編譯器的目標是將這樣的模板：\n\n```html\n<button @click=\"count++\">{{ count }}</button>\n```\n\n轉換成這樣的代碼：\n\n```ts\nimport { template as _template, setText as _setText, on as _on, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  _renderEffect(() => {\n    _setText(_el0, \"\", count.value);\n  });\n  _on(_root, \"click\", () => count++);\n  return _root;\n})\n```\n\n關鍵點是：\n\n1. **靜態部分變成模板字串**：HTML 結構被預先創建為字串\n2. **動態部分通過 renderEffect 處理**：響應式值的變化觸發直接的 DOM 更新\n3. **事件處理器直接附加**：沒有虛擬 DOM 事件委託\n\n## 編譯器架構\n\nVapor 編譯器遵循與常規模板編譯器類似的流程，但採用了更精細的中間表示（IR）方法：\n\n```\n模板 (string)\n  ↓ [Parse]\nAST (抽象語法樹)\n  ↓ [Transform]\nIR (中間表示)\n  ↓ [Codegen]\nVapor 代碼 (string)\n```\n\n## 什麼是 IR（中間表示）？\n\nIR（中間表示）是位於 AST 和最終代碼之間的數據結構．\n使用 IR 的好處包括：\n\n1. **關注點分離**：清晰地分離解析和代碼生成\n2. **易於優化**：在 IR 層面更容易進行靜態分析和優化\n3. **可擴展性**：添加新功能更加簡單\n\n### IR 結構\n\n```ts\n// IR 節點類型\nenum IRNodeTypes {\n  ROOT = \"root\",\n  BLOCK = \"block\",\n  SET_TEXT = \"setText\",\n  SET_EVENT = \"setEvent\",\n  SET_PROP = \"setProp\",\n  IF = \"if\",\n  FOR = \"for\",\n}\n\n// Block IR 節點 - 操作和效果的容器\ninterface BlockIRNode {\n  type: IRNodeTypes.BLOCK;\n  node: RootNode | TemplateChildNode;\n  dynamic: IRDynamicInfo;\n  effect: IREffect[];      // 響應式操作\n  operation: OperationNode[]; // 非響應式操作\n  returns: number[];       // 要返回的元素 ID\n}\n\n// Effect - 響應式依賴和操作的集合\ninterface IREffect {\n  expressions: SimpleExpressionNode[]; // 依賴的表達式\n  operations: OperationNode[];         // 要執行的操作\n}\n```\n\n### 操作節點示例\n\n```ts\n// 文本更新\ninterface SetTextIRNode {\n  type: IRNodeTypes.SET_TEXT;\n  element: number;  // 元素 ID\n  values: SimpleExpressionNode[];\n}\n\n// 事件綁定\ninterface SetEventIRNode {\n  type: IRNodeTypes.SET_EVENT;\n  element: number;\n  key: string;      // 事件名\n  value: SimpleExpressionNode;\n  modifiers?: string[];\n}\n\n// 屬性綁定\ninterface SetPropIRNode {\n  type: IRNodeTypes.SET_PROP;\n  element: number;\n  key: string;\n  value: SimpleExpressionNode;\n}\n```\n\n## Transformer 的作用\n\nTransformer 將 AST 轉換為 IR．\n它遍歷每個 AST 節點並生成適當的 IR 節點．\n\n### TransformContext\n\n```ts\ninterface TransformContext {\n  root: RootIRNode;\n  block: BlockIRNode;\n  template: string;        // 靜態模板字串\n  elementCount: number;\n\n  // 分配元素 ID\n  reference(): number;\n\n  // 註冊響應式效果\n  registerEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void;\n\n  // 註冊非響應式操作\n  registerOperation(...operations: OperationNode[]): void;\n\n  // 進入新區塊（用於 v-if, v-for）\n  enterBlock(block: BlockIRNode): () => void;\n}\n```\n\n### 轉換流程\n\n```ts\nexport function transform(ast: RootNode, source: string): RootIRNode {\n  const ir = createRootIR(ast, source);\n  const context = createTransformContext(ir);\n\n  // 遞迴轉換子元素\n  transformChildren(ast.children, context);\n\n  // 保存模板字串\n  ir.template.push(context.template);\n\n  return ir;\n}\n```\n\n### 指令轉換\n\n每個指令由專用的轉換函數處理：\n\n```ts\n// v-on 轉換\nfunction transformVOn(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const eventName = (dir.arg as SimpleExpressionNode).content;\n\n  // 事件註冊為操作（非響應式）\n  context.registerOperation({\n    type: IRNodeTypes.SET_EVENT,\n    element: elementId,\n    key: eventName,\n    value: dir.exp as SimpleExpressionNode,\n    modifiers: dir.modifiers,\n  });\n}\n\n// v-bind 轉換\nfunction transformVBind(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const propName = (dir.arg as SimpleExpressionNode).content;\n\n  // 註冊為效果（響應式）\n  context.registerEffect([dir.exp as SimpleExpressionNode], [\n    {\n      type: IRNodeTypes.SET_PROP,\n      element: elementId,\n      key: propName,\n      value: dir.exp as SimpleExpressionNode,\n    },\n  ]);\n}\n```\n\n### 常量表達式優化\n\n`registerEffect` 檢查表達式是否為常量，如果是，則將其註冊為普通 `operation` 而不是 `effect`：\n\n```ts\nregisterEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void {\n  // 過濾掉常量表達式\n  const reactiveExpressions = expressions.filter((exp) => !isConstantExpression(exp));\n\n  // 如果沒有響應式依賴，註冊為操作\n  if (reactiveExpressions.length === 0) {\n    context.registerOperation(...operations);\n    return;\n  }\n\n  // 註冊為效果\n  currentBlock.effect.push({\n    expressions: reactiveExpressions,\n    operations,\n  });\n}\n```\n\n## 什麼是 renderEffect？\n\n`renderEffect` 是 Vapor 模式的核心函數．\n與虛擬 DOM 基於差異的方法不同，它直接追蹤響應式依賴並在變化時更新 DOM．\n\n### 工作原理\n\n```ts\n/**\n * renderEffect - Vapor 模式中響應式 DOM 更新的核心機制\n *\n * 1. 將 DOM 更新函數包裝在響應式效果中\n * 2. 自動追蹤訪問了哪些響應式值\n * 3. 當追蹤的值發生變化時重新運行更新函數\n * 4. 只更新需要更改的特定 DOM 節點\n *\n * 重要：renderEffect 還處理生命週期鉤子：\n * - 在每次更新前調用 onBeforeUpdate 鉤子（初始掛載後）\n * - 在每次更新後調用 onUpdated 鉤子（初始掛載後）\n */\nexport const renderEffect = (fn: () => void): void => {\n  const instance = currentInstance;\n\n  effect(() => {\n    // 更新前：調用 onBeforeUpdate 鉤子（僅在掛載後）\n    if (instance?.isMounted) {\n      const { bu } = instance;\n      if (bu) invokeArrayFns(bu);\n    }\n\n    // 執行更新\n    fn();\n\n    // 更新後：調用 onUpdated 鉤子（僅在掛載後）\n    if (instance?.isMounted) {\n      const { u } = instance;\n      if (u) {\n        queueMicrotask(() => invokeArrayFns(u));\n      }\n    }\n  });\n};\n```\n\n### 生成的代碼示例\n\n```ts\n// 模板: <span>{{ count }}</span>\n\nrenderEffect(() => {\n  setText(_el0, \"\", count.value)\n})\n\n// 當 count.value 變化時：\n// 1. 調用 onBeforeUpdate 鉤子\n// 2. 更新文本內容\n// 3. 調用 onUpdated 鉤子（在微任務中）\n```\n\n### 與虛擬 DOM 的比較\n\n| 方面 | 虛擬 DOM | Vapor (renderEffect) |\n|------|----------|---------------------|\n| 更新粒度 | 重新渲染整個組件 | 只更新變化的部分 |\n| 追蹤方法 | 差異算法 | 響應式依賴追蹤 |\n| 開銷 | VNode 創建和比較 | 無（直接 DOM 操作） |\n\n## Codegen 實現\n\nCodegen 從 IR 生成代碼：\n\n```ts\nexport function generateVaporFromIR(ir: RootIRNode, options = {}): VaporCodegenResult {\n  const context = createVaporCodegenContext();\n\n  // 生成前導（導入等）\n  genVaporPreamble(context, options.isBrowser);\n\n  // 生成組件函數\n  push(`((_self) => {`);\n  indent();\n\n  // 生成 template() 調用\n  push(`const _root = _template(\\`${ir.template[0]}\\`);`);\n\n  // 生成元素引用\n  for (let i = 0; i < elementCount; i++) {\n    push(`const _el${i} = _root${generateElementPath(i)};`);\n  }\n\n  // 生成非響應式操作\n  for (const op of block.operation) {\n    genOperation(op, context);\n  }\n\n  // 生成響應式效果\n  for (const effect of block.effect) {\n    push(`_renderEffect(() => {`);\n    indent();\n    for (const op of effect.operations) {\n      genOperation(op, context);\n    }\n    deindent();\n    push(`});`);\n  }\n\n  push(`return _root;`);\n  deindent();\n  push(`})`);\n\n  return { code: context.code, preamble, ast: ir.node };\n}\n```\n\n## 使用示例\n\n```ts\nimport { compile } from \"@chibivue/compiler-vapor\";\n\n// 基於 IR 的編譯\nconst result = compile(`\n  <button @click=\"count++\" :class=\"btnClass\">Count: {{ count }}</button>\n`, { useIR: true });\n\nconsole.log(result.code);\n```\n\n輸出：\n\n```ts\nimport { template as _template, setText as _setText, on as _on, setClass as _setClass, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"\n\n((_self) => {\n  const _root = _template(`<button><!----></button>`);\n  const _el0 = _root.firstChild;\n  const _el1 = _root;\n  _on(_el1, \"click\", count++);\n  _renderEffect(() => {\n    _setClass(_el1, btnClass);\n  });\n  _renderEffect(() => {\n    _setText(_el0, \"\", count);\n  });\n  return _root;\n})\n```\n\n## 總結\n\nVapor 編譯器通過以下流程將模板轉換為代碼：\n\n1. **Parse**：將模板轉換為 AST\n2. **Transform**：將 AST 轉換為 IR（包括優化）\n3. **Codegen**：從 IR 生成代碼\n\n使用 IR 可以：\n- 更容易進行靜態分析和優化\n- 提高代碼可維護性\n- 簡化新功能的添加\n\n使用 `renderEffect`：\n- 可以進行細粒度的響應式更新\n- 消除虛擬 DOM 開銷\n- 只有更改的部分才會被有效更新\n- 自動處理生命週期鉤子（onBeforeUpdate，onUpdated）\n\n在下一節中，我們將了解如何使用 SSR 支持在伺服器上渲染 Vapor 組件．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/90-web-application-essentials/050-vapor/030-vapor-ssr.md",
    "content": "# Vapor SSR\n\n在本節中，我們將探討如何在伺服器端渲染 Vapor 組件．\n由於 Vapor 組件直接操作 DOM，而伺服器上不存在 DOM，因此 Vapor 的 SSR（伺服器端渲染）面臨獨特的挑戰．\n\n## 挑戰\n\nVapor 組件的工作方式：\n1. 使用 `document.createElement` 創建 DOM 元素（通過 `template()`）\n2. 使用 `textContent`，`addEventListener` 等直接操作這些元素\n\n在伺服器上，沒有 `document` 對象．我們需要一種不同的方法來從 Vapor 組件生成 HTML 字串．\n\n## 解決方案\n\nVapor SSR 有兩種主要方法：\n\n1. **Mock DOM**：創建一個捕獲操作並將其轉換為 HTML 的假 DOM 環境\n2. **重用 VNode SSR**：在伺服器端使用標準的 VNode 基礎 SSR，在客戶端作為 Vapor 進行水合\n\nVue.js 的 [PR #13226](https://github.com/vuejs/core/pull/13226) 採用了第二種方法．chibivue 也實現了類似的方法．\n\n<KawaikoNote variant=\"base\" title=\"Vue.js 的方法\">\nVue.js 的 Vapor SSR 在伺服器端使用現有的 VNode 基礎 SSR（compiler-ssr），在客戶端使用 `createVaporSSRApp` 進行水合。這消除了創建單獨 SSR 編譯器的需要。\n</KawaikoNote>\n\n## 實現方式\n\n### 伺服器端：使用 VNode SSR\n\n在 Vapor SSR 中，Vapor 組件在伺服器端被編譯為常規的 VNode 基礎組件．這允許直接使用 `@chibivue/compiler-ssr`．\n\n```ts\n// compiler-sfc/src/compileTemplate.ts\nexport function compileTemplate({\n  source,\n  ssr = false,\n  vapor = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  // 即使在 Vapor + SSR 模式下也使用 compiler-ssr\n  const defaultCompiler = ssr\n    ? (CompilerSSR as TemplateCompiler)\n    : CompilerDOM;\n\n  let { code, ast, preamble } = defaultCompiler.compile(source, {\n    ...compilerOptions,\n    ssr,\n  });\n\n  // 在 Vapor + SSR 模式下添加 __vapor 標誌\n  if (vapor && ssr) {\n    code = code.replace(\n      /export (function|const) ssrRender/,\n      \"export const __vapor = true;\\nexport $1 ssrRender\",\n    );\n  }\n\n  return { code, ast, source, preamble };\n}\n```\n\n`__vapor` 標誌表示在水合時應使用 Vapor 模式．\n\n### 客戶端：createVaporSSRApp\n\n在客戶端，使用 `createVaporSSRApp` 來水合 SSR 渲染的 HTML．\n\n```ts\n// runtime-vapor/src/apiCreateVaporApp.ts\nexport function createVaporSSRApp(rootComponent: VaporComponent): VaporApp {\n  const context = createAppContext();\n\n  const app: VaporApp = {\n    // ... 通用應用配置 ...\n\n    mount(containerOrSelector: Element | string) {\n      const container = typeof containerOrSelector === \"string\"\n        ? document.querySelector(containerOrSelector)\n        : containerOrSelector;\n\n      if (container?.hasChildNodes()) {\n        // 當存在 SSR 內容時進入水合模式\n        const vnode = createVNode(rootComponent as any);\n        vnode.appContext = context;\n        const instance = hydrateVaporComponent(vnode, container, null);\n        app._instance = instance;\n      } else {\n        // 沒有 SSR 內容時進行正常掛載\n        // ...\n      }\n    },\n  };\n\n  return app;\n}\n```\n\n### 水合\n\n水合過程重用現有的 DOM 元素，同時設置響應性和事件監聽器．\n\n```ts\n// runtime-vapor/src/hydration.ts\nexport function hydrateVaporComponent(\n  vnode: VNode,\n  container: Element,\n  parentInstance: VaporComponentInternalInstance | null = null,\n): VaporComponentInternalInstance {\n  const instance = createVaporComponentInstance(vnode, parentInstance);\n\n  // 設置水合上下文\n  const ctx: VaporHydrationContext = {\n    node: container.firstChild,\n    parent: container,\n  };\n\n  setCurrentInstance(instance as any);\n  (instance as any).__hydrationCtx = ctx;\n\n  try {\n    const comp = instance.type as VaporComponent;\n    // 執行組件 - template() 找到現有的 DOM\n    const el = comp(instance);\n\n    // 標記為已掛載\n    instance.isMounted = true;\n\n    // 調用 mounted 鉤子\n    const { m } = instance as any;\n    if (m) invokeArrayFns(m);\n\n    return instance;\n  } finally {\n    unsetCurrentInstance();\n    delete (instance as any).__hydrationCtx;\n  }\n}\n```\n\n## Mock DOM 方法\n\nchibivue 也在 `server-renderer` 中實現了 Mock DOM 方法．當不使用 VNode SSR 時，這可以作為後備方案．\n\n### SSR 元素\n\n我們創建模仿 DOM 元素但將數據存儲在內存中的類：\n\n```ts\nclass SSRElement {\n  tagName: string;\n  attributes: Map<string, string> = new Map();\n  children: (SSRElement | SSRText)[] = [];\n  textContent: string = \"\";\n\n  constructor(tagName: string) {\n    this.tagName = tagName.toLowerCase();\n  }\n\n  setAttribute(name: string, value: string): void {\n    this.attributes.set(name, value);\n  }\n\n  addEventListener(): void {\n    // SSR 中不做任何操作 - 事件僅在客戶端\n  }\n\n  appendChild(child: SSRElement | SSRText): void {\n    this.children.push(child);\n  }\n\n  toHTML(): string {\n    let html = `<${this.tagName}`;\n    for (const [name, value] of this.attributes) {\n      html += ` ${name}=\"${escapeHtml(value)}\"`;\n    }\n    html += \">\";\n\n    if (this.textContent) {\n      html += escapeHtml(this.textContent);\n    } else {\n      for (const child of this.children) {\n        html += child.toHTML();\n      }\n    }\n\n    html += `</${this.tagName}>`;\n    return html;\n  }\n}\n```\n\n## 使用示例\n\n### 伺服器端\n\n```ts\nimport { createVNode } from \"chibivue\";\nimport { renderToString } from \"@chibivue/server-renderer\";\nimport App from \"./App.vue\";\n\n// 將組件渲染為 HTML 字串\nconst html = await renderToString(createVNode(App));\n\n// 發送 HTML 響應\nres.send(`\n<!DOCTYPE html>\n<html>\n  <head><title>My App</title></head>\n  <body>\n    <div id=\"app\">${html}</div>\n    <script type=\"module\" src=\"/src/entry-client.ts\"></script>\n  </body>\n</html>\n`);\n```\n\n### 客戶端\n\n```ts\n// entry-client.ts\nimport { createVaporSSRApp } from \"@chibivue/runtime-vapor\";\nimport App from \"./App.vue\";\n\n// 水合 SSR 渲染的 HTML\ncreateVaporSSRApp(App).mount(\"#app\");\n```\n\n## 與虛擬 DOM SSR 的比較\n\n| 方面 | 虛擬 DOM SSR | Vapor SSR |\n|--------|-----------------|-----------|\n| 伺服器渲染 | 遍歷 VNode 樹，生成 HTML | 相同（使用 VNode SSR） |\n| 客戶端水合 | 使用 VNode diff | 直接引用/操作 DOM |\n| 包大小 | 需要虛擬 DOM 運行時 | 輕量級 Vapor 運行時 |\n| 更新性能 | 經過 diff 算法 | 直接 DOM 操作 |\n\n## 架構優勢\n\nVue.js 風格的 Vapor SSR 方法具有以下優勢：\n\n1. **代碼重用**：可以直接使用現有的 `compiler-ssr`\n2. **一致的輸出**：伺服器生成的 HTML 與常規 VNode SSR 相同\n3. **漸進式遷移**：可以與非 Vapor 組件共存\n4. **可維護性**：無需維護單獨的 SSR 編譯器\n\n<KawaikoNote variant=\"warning\" title=\"需要水合\">\n伺服器渲染的 HTML 是靜態的。為了獲得交互性，你需要在客戶端水合 Vapor 組件，這將設置響應式 effect 和事件監聽器。\n</KawaikoNote>\n\n## 限制\n\n當前實現是最小的，有一些限制：\n\n1. **不支持流式傳輸**：整個組件在返回之前被渲染\n2. **不支持 Suspense**：異步組件的 SSR 支持有限\n3. **水合不匹配**：客戶端和伺服器輸出不同時的警告功能未實現\n\n<KawaikoNote variant=\"base\" title=\"未來改進\">\n更完整的實現將包括：\n- 流式 SSR 支持\n- 水合不匹配檢測\n- Suspense 集成\n</KawaikoNote>\n\n## 總結\n\nVapor SSR 的工作方式如下：\n\n1. **伺服器端**：使用 `compiler-ssr` 生成 HTML 字串（與 VNode SSR 相同）\n2. **客戶端**：使用 `createVaporSSRApp` 進行水合\n3. **水合**：重用現有的 DOM 元素，同時設置響應性\n\n這種方法允許 Vapor 組件享受 SSR 的好處，同時在客戶端獲得直接 DOM 操作的性能優勢．\n"
  },
  {
    "path": "book/online-book/src/zh-tw/bonus/debug-vuejs-core.md",
    "content": "# 除錯原始原始碼\n\n有時您可能想要執行和測試 Vue.js 的實際原始碼．  \n作為本書方法的一部分，我們強烈建議閱讀和理解原始原始碼，以及進行原始碼閱讀和測試實驗．\n\n因此，我們將介紹幾種除錯原始原始碼的方法，這些方法在正文中沒有涉及．\n\n（我們將按照易於理解的順序介紹它們．）\n\n## 利用 SFC Playground\n\n這是最簡單的方法．它廣為人知，甚至在官方文件中也有連結．\n\nhttps://play.vuejs.org\n\n在這個 playground 中，您不僅可以編寫 Vue 組件並檢查它們的行為，還可以檢查 SFC 的編譯結果．  \n它很方便，因為您可以在瀏覽器中快速檢查它．（當然，您也可以分享它．）\n\n<video src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/8281e589-fdaf-4206-854e-25a66dfaac05\" controls />\n\n## 利用 vuejs/core 測試\n\n接下來，讓我們嘗試執行 [vuejs/core](https://github.com/vuejs/core) 的測試．\n當然，您需要複製 [vuejs/core](https://github.com/vuejs/core) 的原始碼．\n\n```bash\ngit clone https://github.com/vuejs/core.git vuejs-core\n# NOTE: 建議使其易於理解，因為儲存庫名稱是 `core`\n```\n\n然後，\n\n```bash\ncd vuejs-core\nni\npnpm test\n```\n\n您可以執行測試，所以請隨意修改您感興趣的原始碼並執行測試．\n\n除了 `test` 之外還有幾個其他的測試指令，如果您感興趣，請檢查 `package.json`．\n\n您可以閱讀和理解測試程式碼，修改程式碼並執行測試，或新增測試案例．有各種使用方法．\n\n<img width=\"590\" alt=\"Screenshot 2024-01-07 0 31 29\" src=\"https://github.com/ubugeeei/ubugeeei/assets/71201308/3c862bd5-1d94-4d2a-a9fa-8755872098ed\">\n\n## 執行 vuejs/core 原始碼\n\n接下來，這是最方便但仍然是實際修改和執行 vuejs/core 原始碼的方法．\n\n關於這一點，我們已經準備了可以與 vite 進行 HMR 的專案，包括 SFC 和獨立版本，所以請嘗試使用它們．\n這個專案在 [chibivue](https://github.com/chibivue-land/chibivue) 的儲存庫中，所以請複製它．\n\n```bash\ngit clone https://github.com/chibivue-land/chibivue.git\n```\n\n複製後，執行腳本來建立專案．\n\n此時，您應該被要求輸入本地 vuejs/core 原始碼的**絕對路徑**，所以請輸入它．\n\n```bash\ncd chibivue\nni\npnpm setup:vue\n\n# 💁 input your local vuejs/core absolute path:\n#   e.g. /Users/ubugeeei/oss/vuejs-core\n#   >\n```\n\n這將在 chibivue 儲存庫中建立一個指向本地 vuejs/core 原始碼的 Vue 專案．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/5d57c022-c411-4452-9e7e-c27623ec28b4\" controls/>\n\n然後，當您想要啟動時，您可以使用以下指令啟動它，並在修改 vuejs/core 原始碼的同時檢查操作．\n\n```bash\npnpm dev:vue\n```\n\n當然，playground 端的 HMR，\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/a2ad46d8-4b07-4ac5-a887-f71507c619a6\" controls/>\n\n即使您修改 vuejs/core 程式碼，HMR 也會工作．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/72f38910-19b8-4171-9ed7-74d1ba223bc8\" controls/>\n\n---\n\n另外，如果您想在獨立模式下檢查它，您也可以透過將 index.html 更改為載入 standalone-vue.js 來使用 HMR．\n\n<video src=\"https://github.com/ubugeeei/work-log/assets/71201308/c57ab5c2-0e62-4971-b1b4-75670d3efeec\" controls/>\n"
  },
  {
    "path": "book/online-book/src/zh-tw/bonus/hyper-ultimate-super-extreme-minimal-vue/15-min-impl.md",
    "content": "# Hyper Ultimate Super Extreme Minimal Vue\n\n## 專案設定（0.5 分鐘）\n\n```sh\n# 複製此儲存庫並導航到它。\ngit clone https://github.com/chibivue-land/chibivue\ncd chibivue\n\n# 使用設定指令建立專案。\n# 將專案的根路徑指定為參數。\npnpm setup ../my-chibivue-project\n```\n\n專案設定現在完成了．\n\n讓我們現在實現 packages/index.ts．\n\n## createApp（1 分鐘）\n\n對於 create app 函式，讓我們考慮一個允許指定 setup 和 render 函式的簽名．從使用者的角度來看，它將這樣使用：\n\n```ts\nconst app = createApp({\n  setup() {\n    // TODO:\n  },\n  render() {\n    // TODO:\n  },\n})\n\napp.mount('#app')\n```\n\n讓我們實現它：\n\n```ts\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\n```\n\n然後我們可以回傳一個實現 mount 函式的物件：\n\n```ts\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    // TODO: patch rendering\n  },\n})\n```\n\n這部分就是這樣．\n\n## h 函式和虛擬 DOM（0.5 分鐘）\n\n要執行補丁渲染，我們需要虛擬 DOM 和產生它的函式．\n\n虛擬 DOM 使用 JavaScript 物件表示標籤名稱，屬性和子元素．Vue 渲染器處理虛擬 DOM 並將更新應用到實際 DOM．\n\n讓我們考慮一個 VNode，它表示一個名稱，一個點擊事件處理程式和子元素（文字）：\n\n```ts\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n```\n\n這部分就是這樣．\n\n## 補丁渲染（2 分鐘）\n\n現在讓我們實現渲染器．\n\n這個渲染過程通常被稱為補丁，因為它比較舊的和新的虛擬 DOM 並將差異應用到實際 DOM．\n\n函式簽名將是：\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  // TODO:\n}\n```\n\nn1 表示舊的 VNode，n2 表示新的 VNode，container 是實際 DOM 的根．在這個例子中，`#app` 將是容器（使用 createApp 掛載的元素）．\n\n我們需要考慮兩種類型的操作：\n\n- 掛載  \n  這是初始渲染．如果 n1 為 null，意味著這是第一次渲染，所以我們需要實現掛載過程．\n- 補丁  \n  這比較 VNode 並將差異應用到實際 DOM．  \n  但是這次，我們只更新子元素而不檢測差異．\n\n讓我們實現它：\n\n```ts\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n```\n\n這部分就是這樣．\n\n## 響應式系統（2 分鐘）\n\n現在讓我們實現邏輯來追蹤在 setup 選項中定義的狀態變化並觸發 render 函式．這個追蹤狀態變化並執行特定操作的過程稱為\"響應式系統\"．\n\n讓我們考慮使用 `reactive` 函式來定義狀態：\n\n```ts\nconst app = createApp({\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n  // ..\n  // ..\n})\n```\n\n在這種情況下，當使用 `reactive` 函式定義的狀態被修改時，我們希望觸發補丁過程．\n\n它可以使用 Proxy 物件來實現這一點．代理允許我們為 get/set 操作實現功能．在這種情況下，我們可以使用 set 操作在發生 set 操作時執行補丁過程．\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      // ??? 這裡我們想要執行補丁過程\n      return res\n    },\n  })\n```\n\n問題是，我們應該在 set 操作中觸發什麼？通常，我們會使用 get 操作來追蹤變化，但在這種情況下，我們將在全域範圍內定義一個 `update` 函式並引用它．\n\n讓我們使用之前實現的 render 函式來建立 update 函式：\n\n```ts\nlet update: (() => void) | null = null // 我們想要用 Proxy 引用這個，所以它需要在全域範圍內\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup() // 只在第一次渲染時執行 setup\n    update = () => {\n      // 產生一個閉包來比較 prevVNode 和 VNode\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n```\n\n現在我們只需要在 Proxy 的 set 操作中呼叫它：\n\n```ts\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.() // 執行更新\n      return res\n    },\n  })\n```\n\n就是這樣！\n\n## 模板編譯器（5 分鐘）\n\n到目前為止，我們已經能夠透過允許使用者使用 render 選項和 h 函式來實現宣告式 UI．但是，實際上，我們希望以類似 HTML 的方式編寫它．\n\n因此，讓我們實現一個模板編譯器，將 HTML 轉換為 h 函式．\n\n目標是將這樣的字串：\n\n```\n<button @click=\"increment\">state: {{ state.count }}</button>\n```\n\n轉換為這樣的函式：\n\n```\nh(\"button\", increment, \"state: \" + state.count)\n```\n\n讓我們稍微分解一下．\n\n- parse  \n  解析 HTML 字串並將其轉換為稱為 AST（抽象語法樹）的物件．\n- codegen  \n  基於 AST 產生所需的程式碼（字串）．\n\n現在，讓我們實現 AST 和 parse．\n\n```ts\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\n```\n\n我們這次處理的 AST 如上所示．它類似於 VNode，但完全不同，用於產生程式碼．Interpolation 表示鬍鬚語法．像 <span v-pre>`{{ state.count }}`</span> 這樣的字串被解析為像 <span v-pre>`{ content: \"state.count\" }`</span> 這樣的物件（AST）．\n\n接下來，讓我們實現從給定字串產生 AST 的 parse 函式．現在，讓我們使用正規表示式和一些字串操作快速實現它．\n\n```ts\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\n```\n\n接下來是 codegen．基於 AST 產生 h 函式的呼叫．\n\n```ts\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\n```\n\n狀態從參數 `_ctx` 中引用．\n\n透過組合這些，我們可以完成 compile 函式．\n\n```ts\nconst compile = (template: string): string => codegen(parse(template))\n```\n\n好吧，實際上，就目前而言，它只是產生 h 函式呼叫的字串，所以它還不能工作．\n\n我們將與 sfc 編譯器一起實現它．\n\n有了這個，模板編譯器就完成了．\n\n## sfc 編譯器（vite-plugin）（4 分鐘）\n\n最後！讓我們實現一個 vite 外掛來支援 sfc．\n\n在 vite 外掛中，有一個名為 transform 的選項，它允許您轉換檔案的內容．\n\ntransform 函式回傳類似 `{ code: string }` 的東西，字串被視為原始碼．換句話說，例如，\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: \"vite-plugin-chibivue\",\n  transform: (code: string, id: string) => ({\n    code: \"\";\n  }),\n});\n```\n\n將使所有檔案的內容成為空字串．原始程式碼可以作為第一個參數接收，所以透過正確轉換這個值並在最後回傳它，您可以轉換它．\n\n有 5 件事要做．\n\n- 從腳本中提取作為預設匯出的內容．\n- 將其轉換為將其分配給變數的程式碼．（為了方便，讓我們稱變數為 A．）\n- 從模板中提取 HTML 字串，並使用我們之前建立的 compile 函式將其轉換為對 h 函式的呼叫．（為了方便，讓我們稱結果為 B．）\n- 產生類似 `Object.assign(A, { render: B })` 的程式碼．\n- 產生將 A 作為預設匯出的程式碼．\n\n現在讓我們實現它．\n\n```ts\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\n之後，在外掛中實現它．\n\n```ts\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : code, // 僅適用於 .vue 副檔名的檔案\n})\n```\n\n## 結束\n\n是的．有了這個，我們已經成功實現到 SFC．\n讓我們再看一下原始碼．\n\n```ts\n// create app api\ntype CreateAppOption = {\n  setup: () => Record<string, unknown>\n  render: (ctx: Record<string, unknown>) => VNode\n}\nlet update: (() => void) | null = null\nexport const createApp = (option: CreateAppOption) => ({\n  mount(selector: string) {\n    const container = document.querySelector(selector)!\n    let prevVNode: VNode | null = null\n    const setupState = option.setup()\n    update = () => {\n      const vnode = option.render(setupState)\n      render(prevVNode, vnode, container)\n      prevVNode = vnode\n    }\n    update()\n  },\n})\n\n// Virtual DOM patch\nexport const render = (n1: VNode | null, n2: VNode, container: Element) => {\n  const mountElement = (vnode: VNode, container: Element) => {\n    const el = document.createElement(vnode.tag)\n    el.textContent = vnode.children\n    el.addEventListener('click', vnode.onClick)\n    container.appendChild(el)\n  }\n  const patchElement = (_n1: VNode, n2: VNode) => {\n    ;(container.firstElementChild as Element).textContent = n2.children\n  }\n  n1 == null ? mountElement(n2, container) : patchElement(n1, n2)\n}\n\n// Virtual DOM\ntype VNode = { tag: string; onClick: (e: Event) => void; children: string }\nexport const h = (\n  tag: string,\n  onClick: (e: Event) => void,\n  children: string,\n): VNode => ({ tag, onClick, children })\n\n// Reactivity System\nexport const reactive = <T extends Record<string, unknown>>(obj: T): T =>\n  new Proxy(obj, {\n    get: (target, key, receiver) => Reflect.get(target, key, receiver),\n    set: (target, key, value, receiver) => {\n      const res = Reflect.set(target, key, value, receiver)\n      update?.()\n      return res\n    },\n  })\n\n// template compiler\ntype AST = {\n  tag: string\n  onClick: string\n  children: (string | Interpolation)[]\n}\ntype Interpolation = { content: string }\nconst parse = (template: string): AST => {\n  const RE = /<([a-z]+)\\s@click=\\\"([a-z]+)\\\">(.+)<\\/[a-z]+>/\n  const [_, tag, onClick, children] = template.match(RE) || []\n  if (!tag || !onClick || !children) throw new Error('Invalid template!')\n  const regex = /{{(.*?)}}/g\n  let match: RegExpExecArray | null\n  let lastIndex = 0\n  const parsedChildren: AST['children'] = []\n  while ((match = regex.exec(children)) !== null) {\n    lastIndex !== match.index &&\n      parsedChildren.push(children.substring(lastIndex, match.index))\n    parsedChildren.push({ content: match[1].trim() })\n    lastIndex = match.index + match[0].length\n  }\n  lastIndex < children.length && parsedChildren.push(children.substr(lastIndex))\n  return { tag, onClick, children: parsedChildren }\n}\nconst codegen = (node: AST) =>\n  `(_ctx) => h('${node.tag}', _ctx.${node.onClick}, \\`${node.children\n    .map(child =>\n      typeof child === 'object' ? `\\$\\{_ctx.${child.content}\\}` : child,\n    )\n    .join('')}\\`)`\nconst compile = (template: string): string => codegen(parse(template))\n\n// sfc compiler (vite transformer)\nexport const VitePluginChibivue = () => ({\n  name: 'vite-plugin-chibivue',\n  transform: (code: string, id: string) =>\n    id.endsWith('.vue') ? compileSFC(code) : null,\n})\nconst compileSFC = (sfc: string): { code: string } => {\n  const [_, scriptContent] =\n    sfc.match(/<script>\\s*([\\s\\S]*?)\\s*<\\/script>/) ?? []\n  const [___, defaultExported] =\n    scriptContent.match(/export default\\s*([\\s\\S]*)/) ?? []\n  const [__, templateContent] =\n    sfc.match(/<template>\\s*([\\s\\S]*?)\\s*<\\/template>/) ?? []\n  if (!scriptContent || !defaultExported || !templateContent)\n    throw new Error('Invalid SFC!')\n  let code = ''\n  code +=\n    \"import { h, reactive } from 'hyper-ultimate-super-extreme-minimal-vue';\\n\"\n  code += `const options = ${defaultExported}\\n`\n  code += `Object.assign(options, { render: ${compile(templateContent)} });\\n`\n  code += 'export default options;\\n'\n  return { code }\n}\n```\n\n令人驚訝的是，我們能夠在大約 110 行中實現它．（現在沒有人會抱怨了，呼...）\n\n請確保也嘗試主要部分的主要部分！！（雖然這只是一個附錄）\n"
  },
  {
    "path": "book/online-book/src/zh-tw/bonus/hyper-ultimate-super-extreme-minimal-vue/index.md",
    "content": "# chibivue？哪裡小了！？太大了，我處理不了！\n\n## 它很大...\n\n對於那些這樣想的人，我真誠地道歉．\n\n在拿起這本書之前，您可能想像的是更小的東西．\n\n請允許我稍作辯解，即使是我也沒有打算把它做得這麼大．\n\n當我繼續工作時，我發現它很有趣，並想，\"哦，我下一步應該新增這個功能嗎？\"就這樣變成了這樣．\n\n## 明白了．讓我們設定一個時間限制．\n\n導致它變得太大的因素之一是\"沒有時間限制\"．\n\n所以，在這個附錄中，我將嘗試在\"**15 分鐘**\"內實現它．\n\n當然，我也會將解釋限制在一頁內．\n\n此外，不僅是頁面，\"實現本身將包含在一個檔案中\"也是我將嘗試實現的目標．\n\n但是，即使是一個檔案，在一個檔案中寫 100,000 行也是沒有意義的，所以我將目標是在少於 150 行內實現它．\n\n![Full source of Hyper Ultimate Super Extreme Minimal Vue in one file](/figures/bonus/hyper-ultimate-super-extreme-minimal-vue/full-source-screenshot.png)\n\n標題是\"**Hyper Ultimate Super Extreme Minimal Vue**\"．\n\n::: info 關於名稱\n\n我想很多人認為這個名字相當幼稚．\n\n我也這麼認為．\n\n但是，這個名字有一個合適的理由．\n\n在強調它極其小的同時，我想要一個縮寫，所以就變成了這個詞序．\n\n縮寫是\"HUSEM Vue (Balloon Vue)\"．\n\n\"HU-SEN\" [fuːsen] 在日語中意思是\"氣球\"．\n\n雖然我現在將以一種非常草率的方式實現它，但我將這種草率比作一個\"氣球\"，即使針碰到它也會爆炸．\n\n:::\n\n## 你只是要實現一個響應式系統，對吧？\n\n不，不是這樣的．這次，我將嘗試列出將在 15 分鐘內實現的內容．\n\n- create app api\n- Virtual DOM\n- patch rendering\n- Reactivity System\n- template compiler\n- sfc compiler (vite-plugin)\n\n我將實現這些東西．\n\n換句話說，SFC 將工作．\n\n至於原始碼，我假設以下內容將工作：\n\n```vue\n<script>\nimport { reactive } from 'hyper-ultimate-super-extreme-minimal-vue'\n\nexport default {\n  setup() {\n    const state = reactive({ count: 0 })\n    const increment = () => state.count++\n    return { state, increment }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"increment\">state: {{ state.count }}</button>\n</template>\n```\n\n```ts\nimport { createApp } from 'hyper-ultimate-super-extreme-minimal-vue'\n\n// @ts-ignore\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n```\n"
  },
  {
    "path": "book/online-book/src/zh-tw/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: \"chibivue\"\n  text: \"從一行 \\\"Hello, World\\\" 開始，逐步構建\"\n  tagline: 基於 VitePress 構建\n  image: /figures/_brand/logo.png\n  actions:\n    - theme: brand\n      text: 開始閱讀 ->\n      link: /zh-tw/00-introduction/010-about\n    - theme: alt\n      text: Vue.js 官方文檔\n      link: https://vuejs.org/\n\nfeatures:\n  - title: 響應式系統\n    details: 從響應式系統的基本原理開始，我們將涵蓋廣泛的實現，從 EffectScope 到 CustomRef 等高級 API。\n  - title: 虛擬 DOM\n    details: 我們將涵蓋廣泛的實現，從虛擬 DOM 的基本設置到補丁渲染和調度器實現。\n  - title: 模板編譯器\n    details: 從模板編譯器的基礎實現開始，我們將擴展到數據綁定和指令實現。\n  - title: 單文件組件\n    details: 從 SFC 的基本實現開始，我們將深入廣泛的領域，從 script setup 到編譯器宏和作用域 CSS 實現。\n---\n"
  },
  {
    "path": "book/playground/.gitignore",
    "content": "node_modules\ndist\n*.local\n.DS_Store\nsrc/chapters.generated.ts\n"
  },
  {
    "path": "book/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/png\" href=\"https://raw.githubusercontent.com/chibivue-land/chibivue/main/book/online-book/src/public/figures/_brand/logo.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue Playground</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "book/playground/package.json",
    "content": "{\n  \"name\": \"@chibivue/book-playground\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"generate\": \"node --experimental-strip-types scripts/generate-chapters.ts\"\n  },\n  \"dependencies\": {\n    \"@mdi/font\": \"^7.4.47\",\n    \"@monaco-editor/loader\": \"^1.5.0\",\n    \"@webcontainer/api\": \"^1.5.1\",\n    \"monaco-editor\": \"^0.52.2\",\n    \"vue\": \"^3.5.16\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue\": \"^6.0.5\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^8.0.0\",\n    \"vue-tsc\": \"^2.2.10\"\n  }\n}\n"
  },
  {
    "path": "book/playground/scripts/generate-chapters.ts",
    "content": "import { readdirSync, readFileSync, writeFileSync, statSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst IMPLS_DIR = join(__dirname, \"../../impls\");\nconst OUTPUT_FILE = join(__dirname, \"../src/chapters.generated.ts\");\n\ninterface ChapterFile {\n  path: string;\n  content: string;\n}\n\ninterface Chapter {\n  id: string;\n  section: string;\n  sectionOrder: string;\n  name: string;\n  files: ChapterFile[];\n  bookUrl: string;\n  vueDocUrl?: string;\n}\n\n// Section display names\nconst SECTION_NAMES: Record<string, string> = {\n  \"00_introduction\": \"Introduction\",\n  \"10_minimum_example\": \"Minimum Example\",\n  \"20_basic_virtual_dom\": \"Basic Virtual DOM\",\n  \"30_basic_reactivity_system\": \"Basic Reactivity System\",\n  \"40_basic_component_system\": \"Basic Component System\",\n  \"50_basic_template_compiler\": \"Basic Template Compiler\",\n  \"60_basic_sfc_compiler\": \"Basic SFC Compiler\",\n  \"90_web_application_essentials\": \"Web Application Essentials\",\n  bonus: \"Bonus\",\n};\n\n// Display names for chapters (more descriptive than directory names)\nconst CHAPTER_DISPLAY_NAMES: Record<string, string> = {\n  // 10_minimum_example\n  \"10_minimum_example/010_create_app\": \"createApp\",\n  \"10_minimum_example/015_package_architecture\": \"Package Architecture\",\n  \"10_minimum_example/020_simple_h_function\": \"h Function\",\n  \"10_minimum_example/030_reactive_system\": \"Reactive System\",\n  \"10_minimum_example/040_vdom_system\": \"Virtual DOM\",\n  \"10_minimum_example/050_component_system\": \"Component\",\n  \"10_minimum_example/050_component_system2\": \"Component Props\",\n  \"10_minimum_example/050_component_system3\": \"Component Emits\",\n  \"10_minimum_example/060_template_compiler\": \"Template Compiler\",\n  \"10_minimum_example/060_template_compiler2\": \"Template Compiler (Impl)\",\n  \"10_minimum_example/060_template_compiler3\": \"Complex Parser\",\n  \"10_minimum_example/070_sfc_compiler\": \"SFC Parser\",\n  \"10_minimum_example/070_sfc_compiler2\": \"SFC Template\",\n  \"10_minimum_example/070_sfc_compiler3\": \"SFC Script\",\n  \"10_minimum_example/070_sfc_compiler4\": \"SFC Style\",\n  // 20_basic_virtual_dom\n  \"20_basic_virtual_dom/010_patch_keyed_children\": \"Keyed Children Patch\",\n  \"20_basic_virtual_dom/020_bit_flags\": \"Bit Flags\",\n  \"20_basic_virtual_dom/040_scheduler\": \"Scheduler\",\n  \"20_basic_virtual_dom/050_next_tick\": \"nextTick\",\n  \"20_basic_virtual_dom/060_other_props\": \"Other Props\",\n  // 30_basic_reactivity_system\n  \"30_basic_reactivity_system/010_ref\": \"ref\",\n  \"30_basic_reactivity_system/020_shallow_ref\": \"shallowRef\",\n  \"30_basic_reactivity_system/030_to_ref\": \"toRef\",\n  \"30_basic_reactivity_system/040_to_refs\": \"toRefs\",\n  \"30_basic_reactivity_system/050_computed\": \"computed\",\n  \"30_basic_reactivity_system/060_watch\": \"watch\",\n  \"30_basic_reactivity_system/070_watch_effect\": \"watchEffect\",\n  \"30_basic_reactivity_system/080_reactive_proxy_handlers\": \"Reactive Proxy\",\n  \"30_basic_reactivity_system/090_effect_scope\": \"effectScope\",\n  \"30_basic_reactivity_system/100_other_apis\": \"Other APIs\",\n  // 40_basic_component_system\n  \"40_basic_component_system/010_lifecycle_hooks\": \"Lifecycle Hooks\",\n  \"40_basic_component_system/020_provide_inject\": \"provide / inject\",\n  \"40_basic_component_system/030_component_proxy\": \"Component Proxy\",\n  \"40_basic_component_system/040_slots\": \"Slots\",\n  \"40_basic_component_system/050_options_api\": \"Options API\",\n  // 50_basic_template_compiler\n  \"50_basic_template_compiler/010_transform\": \"Transform\",\n  \"50_basic_template_compiler/020_v_bind\": \"v-bind\",\n  \"50_basic_template_compiler/022_transform_expression\": \"Expression\",\n  \"50_basic_template_compiler/025_v_on\": \"v-on\",\n  \"50_basic_template_compiler/027_event_modifier\": \"Event Modifier\",\n  \"50_basic_template_compiler/030_fragment\": \"Fragment\",\n  \"50_basic_template_compiler/035_comment\": \"Comment\",\n  \"50_basic_template_compiler/040_v_if\": \"v-if\",\n  \"50_basic_template_compiler/050_v_for\": \"v-for\",\n  \"50_basic_template_compiler/060_v_model\": \"v-model\",\n  \"50_basic_template_compiler/070_resolve_component\": \"resolveComponent\",\n  \"50_basic_template_compiler/080_slot_outlet\": \"Slot Outlet\",\n  \"50_basic_template_compiler/085_slot_insert\": \"Slot Insert\",\n  \"50_basic_template_compiler/090_other_directives\": \"Other Directives\",\n  \"50_basic_template_compiler/100_chore_compiler\": \"Compiler Refactor\",\n  \"50_basic_template_compiler/110_parser_optimization\": \"Parser Optimization\",\n  \"50_basic_template_compiler/500_custom_directive\": \"Custom Directive\",\n  // 60_basic_sfc_compiler\n  \"60_basic_sfc_compiler/010_script_setup\": \"script setup\",\n  \"60_basic_sfc_compiler/020_define_props\": \"defineProps\",\n  \"60_basic_sfc_compiler/030_define_emits\": \"defineEmits\",\n  \"60_basic_sfc_compiler/040_scoped_css\": \"Scoped CSS\",\n  \"60_basic_sfc_compiler/050_props_destructure\": \"Props Destructure\",\n  \"60_basic_sfc_compiler/060_type_based_macros\": \"Type-based Macros\",\n  // 90_web_application_essentials\n  \"90_web_application_essentials/010_router\": \"Router\",\n  \"90_web_application_essentials/020_preprocessors\": \"Preprocessors\",\n  // bonus\n  \"bonus/hyper_ultimate_super_extreme_minimal_vue\": \"15-min Vue\",\n};\n\n// Mapping from impl directory names to book page names\n// Format: \"section/chapter\" -> \"book-page-name\" (without .md extension)\nconst CHAPTER_TO_BOOK_MAPPING: Record<string, string> = {\n  // 10_minimum_example\n  \"10_minimum_example/010_create_app\": \"010-create-app-api\",\n  \"10_minimum_example/015_package_architecture\": \"015-package-architecture\",\n  \"10_minimum_example/020_simple_h_function\": \"020-simple-h-function\",\n  \"10_minimum_example/030_reactive_system\": \"035-try-implementing-a-minimum-reactivity-system\",\n  \"10_minimum_example/040_vdom_system\": \"040-minimum-virtual-dom\",\n  \"10_minimum_example/050_component_system\": \"050-minimum-component\",\n  \"10_minimum_example/050_component_system2\": \"051-component-props\",\n  \"10_minimum_example/050_component_system3\": \"052-component-emits\",\n  \"10_minimum_example/060_template_compiler\": \"060-template-compiler\",\n  \"10_minimum_example/060_template_compiler2\": \"061-template-compiler-impl\",\n  \"10_minimum_example/060_template_compiler3\": \"070-more-complex-parser\",\n  \"10_minimum_example/070_sfc_compiler\": \"091-parse-sfc\",\n  \"10_minimum_example/070_sfc_compiler2\": \"092-compile-sfc-template\",\n  \"10_minimum_example/070_sfc_compiler3\": \"093-compile-sfc-script\",\n  \"10_minimum_example/070_sfc_compiler4\": \"094-compile-sfc-style\",\n  // 20_basic_virtual_dom\n  \"20_basic_virtual_dom/010_patch_keyed_children\": \"010-patch-keyed-children\",\n  \"20_basic_virtual_dom/020_bit_flags\": \"020-bit-flags\",\n  \"20_basic_virtual_dom/040_scheduler\": \"030-scheduler\",\n  \"20_basic_virtual_dom/050_next_tick\": \"030-scheduler\",\n  \"20_basic_virtual_dom/060_other_props\": \"040-patch-other-attrs\",\n  // 30_basic_reactivity_system\n  \"30_basic_reactivity_system/010_ref\": \"010-ref-api\",\n  \"30_basic_reactivity_system/020_shallow_ref\": \"010-ref-api\",\n  \"30_basic_reactivity_system/030_to_ref\": \"010-ref-api\",\n  \"30_basic_reactivity_system/040_to_refs\": \"010-ref-api\",\n  \"30_basic_reactivity_system/050_computed\": \"020-computed-watch\",\n  \"30_basic_reactivity_system/060_watch\": \"020-computed-watch\",\n  \"30_basic_reactivity_system/070_watch_effect\": \"020-computed-watch\",\n  \"30_basic_reactivity_system/080_reactive_proxy_handlers\": \"030-reactive-proxy-handlers\",\n  \"30_basic_reactivity_system/090_effect_scope\": \"040-effect-scope\",\n  \"30_basic_reactivity_system/100_other_apis\": \"050-other-apis\",\n  // 40_basic_component_system\n  \"40_basic_component_system/010_lifecycle_hooks\": \"010-lifecycle-hooks\",\n  \"40_basic_component_system/020_provide_inject\": \"020-provide-inject\",\n  \"40_basic_component_system/030_component_proxy\": \"030-component-proxy-setup-context\",\n  \"40_basic_component_system/040_slots\": \"040-component-slot\",\n  \"40_basic_component_system/050_options_api\": \"050-options-api\",\n  // 50_basic_template_compiler\n  \"50_basic_template_compiler/010_transform\": \"010-transform\",\n  \"50_basic_template_compiler/020_v_bind\": \"020-v-bind\",\n  \"50_basic_template_compiler/022_transform_expression\": \"022-transform-expression\",\n  \"50_basic_template_compiler/025_v_on\": \"025-v-on\",\n  \"50_basic_template_compiler/027_event_modifier\": \"027-event-modifier\",\n  \"50_basic_template_compiler/030_fragment\": \"030-fragment\",\n  \"50_basic_template_compiler/035_comment\": \"035-comment\",\n  \"50_basic_template_compiler/040_v_if\": \"040-v-if-and-structural-directive\",\n  \"50_basic_template_compiler/050_v_for\": \"050-v-for\",\n  \"50_basic_template_compiler/060_v_model\": \"060-v-model\",\n  \"50_basic_template_compiler/070_resolve_component\": \"070-resolve-component\",\n  \"50_basic_template_compiler/080_slot_outlet\": \"080-slot\",\n  \"50_basic_template_compiler/085_slot_insert\": \"080-slot\",\n  \"50_basic_template_compiler/090_other_directives\": \"090-other-directives\",\n  \"50_basic_template_compiler/100_chore_compiler\": \"100-chore-compiler\",\n  \"50_basic_template_compiler/110_parser_optimization\": \"110-parser-optimization\",\n  \"50_basic_template_compiler/500_custom_directive\": \"500-custom-directive\",\n  // 60_basic_sfc_compiler\n  \"60_basic_sfc_compiler/010_script_setup\": \"010-script-setup\",\n  \"60_basic_sfc_compiler/020_define_props\": \"020-define-props\",\n  \"60_basic_sfc_compiler/030_define_emits\": \"030-define-emits\",\n  \"60_basic_sfc_compiler/040_scoped_css\": \"040-scoped-css\",\n  \"60_basic_sfc_compiler/050_props_destructure\": \"050-props-destructure\",\n  \"60_basic_sfc_compiler/060_type_based_macros\": \"060-type-based-macros\",\n  // 90_web_application_essentials\n  \"90_web_application_essentials/010_router\": \"010-plugins/010-router\",\n  \"90_web_application_essentials/020_preprocessors\": \"010-plugins/020-preprocessors\",\n  // bonus\n  \"bonus/hyper_ultimate_super_extreme_minimal_vue\":\n    \"hyper-ultimate-super-extreme-minimal-vue/15-min-impl\",\n};\n\n// Vue.js documentation URLs mapped by chapter keywords\nconst VUE_DOC_URLS: Record<string, string> = {\n  create_app: \"https://vuejs.org/api/application.html#createapp\",\n  h_function: \"https://vuejs.org/api/render-function.html#h\",\n  virtual_dom: \"https://vuejs.org/guide/extras/rendering-mechanism.html#virtual-dom\",\n  reactivity: \"https://vuejs.org/guide/essentials/reactivity-fundamentals.html\",\n  reactive: \"https://vuejs.org/api/reactivity-core.html#reactive\",\n  ref: \"https://vuejs.org/api/reactivity-core.html#ref\",\n  computed: \"https://vuejs.org/api/reactivity-core.html#computed\",\n  watch: \"https://vuejs.org/api/reactivity-core.html#watch\",\n  component: \"https://vuejs.org/guide/essentials/component-basics.html\",\n  props: \"https://vuejs.org/guide/components/props.html\",\n  emits: \"https://vuejs.org/guide/components/events.html\",\n  slots: \"https://vuejs.org/guide/components/slots.html\",\n  provide: \"https://vuejs.org/guide/components/provide-inject.html\",\n  inject: \"https://vuejs.org/guide/components/provide-inject.html\",\n  template: \"https://vuejs.org/guide/essentials/template-syntax.html\",\n  v_bind: \"https://vuejs.org/api/built-in-directives.html#v-bind\",\n  v_on: \"https://vuejs.org/api/built-in-directives.html#v-on\",\n  v_if: \"https://vuejs.org/api/built-in-directives.html#v-if\",\n  v_for: \"https://vuejs.org/api/built-in-directives.html#v-for\",\n  v_model: \"https://vuejs.org/api/built-in-directives.html#v-model\",\n  sfc: \"https://vuejs.org/guide/scaling-up/sfc.html\",\n  script_setup: \"https://vuejs.org/api/sfc-script-setup.html\",\n  scoped_css: \"https://vuejs.org/api/sfc-css-features.html#scoped-css\",\n  css_modules: \"https://vuejs.org/api/sfc-css-features.html#css-modules\",\n  lifecycle: \"https://vuejs.org/guide/essentials/lifecycle.html\",\n  scheduler: \"https://vuejs.org/api/general.html#nexttick\",\n  transition: \"https://vuejs.org/guide/built-ins/transition.html\",\n  teleport: \"https://vuejs.org/guide/built-ins/teleport.html\",\n  suspense: \"https://vuejs.org/guide/built-ins/suspense.html\",\n  keep_alive: \"https://vuejs.org/guide/built-ins/keep-alive.html\",\n  custom_directive: \"https://vuejs.org/guide/reusability/custom-directives.html\",\n  composition_api: \"https://vuejs.org/guide/extras/composition-api-faq.html\",\n  compiler: \"https://vuejs.org/guide/extras/rendering-mechanism.html#templates-vs-render-functions\",\n};\n\nfunction getVueDocUrl(chapterDir: string): string | undefined {\n  const normalized = chapterDir.toLowerCase();\n  for (const [keyword, url] of Object.entries(VUE_DOC_URLS)) {\n    if (normalized.includes(keyword.replace(/_/g, \"\"))) {\n      return url;\n    }\n  }\n  return undefined;\n}\n\nfunction getBookUrl(section: string, chapterDir: string): string {\n  const key = `${section}/${chapterDir}`;\n  const mappedPage = CHAPTER_TO_BOOK_MAPPING[key];\n\n  if (mappedPage) {\n    const sectionSlug = section.replace(/_/g, \"-\");\n    // Handle nested paths like \"010-plugins/010-router\"\n    if (mappedPage.includes(\"/\")) {\n      return `/ja/${sectionSlug}/${mappedPage}.html`;\n    }\n    return `/ja/${sectionSlug}/${mappedPage}.html`;\n  }\n\n  // Fallback: convert underscore to hyphen\n  const sectionSlug = section.replace(/_/g, \"-\");\n  const chapterSlug = chapterDir.replace(/_/g, \"-\");\n  return `/ja/${sectionSlug}/${chapterSlug}.html`;\n}\n\n// Files to exclude from reading\nconst EXCLUDE_FILES = [\n  \".gitignore\",\n  \"node_modules\",\n  \"dist\",\n  \".DS_Store\",\n  \"pnpm-lock.yaml\",\n  \"package-lock.json\",\n];\n\n// File extensions to include\nconst INCLUDE_EXTENSIONS = [\".ts\", \".js\", \".vue\", \".html\", \".css\", \".json\"];\n\nfunction shouldIncludeFile(filename: string): boolean {\n  if (EXCLUDE_FILES.some((exclude) => filename.includes(exclude))) {\n    return false;\n  }\n  return INCLUDE_EXTENSIONS.some((ext) => filename.endsWith(ext));\n}\n\nfunction readFilesRecursively(dir: string, basePath: string = \"\"): ChapterFile[] {\n  const files: ChapterFile[] = [];\n\n  if (!existsSync(dir)) return files;\n\n  const entries = readdirSync(dir);\n\n  for (const entry of entries) {\n    const fullPath = join(dir, entry);\n    const relativePath = basePath ? `${basePath}/${entry}` : entry;\n\n    if (EXCLUDE_FILES.some((exclude) => entry.includes(exclude))) {\n      continue;\n    }\n\n    const stat = statSync(fullPath);\n\n    if (stat.isDirectory()) {\n      files.push(...readFilesRecursively(fullPath, relativePath));\n    } else if (shouldIncludeFile(entry)) {\n      try {\n        const content = readFileSync(fullPath, \"utf-8\");\n        files.push({ path: relativePath, content });\n      } catch {\n        // Skip files that can't be read\n      }\n    }\n  }\n\n  return files;\n}\n\nfunction getChapterName(section: string, chapterDir: string): string {\n  const key = `${section}/${chapterDir}`;\n  const displayName = CHAPTER_DISPLAY_NAMES[key];\n  if (displayName) {\n    return displayName;\n  }\n\n  // Fallback: Remove the numeric prefix (e.g., \"010_create_app\" -> \"create_app\")\n  const withoutPrefix = chapterDir.replace(/^\\d+_/, \"\");\n  // Convert to title case\n  return withoutPrefix\n    .split(\"_\")\n    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n    .join(\" \");\n}\n\nfunction loadChapters(): Chapter[] {\n  const chapters: Chapter[] = [];\n\n  if (!existsSync(IMPLS_DIR)) {\n    console.warn(\"Impls directory not found:\", IMPLS_DIR);\n    return chapters;\n  }\n\n  const sections = readdirSync(IMPLS_DIR).sort();\n\n  for (const section of sections) {\n    const sectionPath = join(IMPLS_DIR, section);\n    const sectionStat = statSync(sectionPath);\n\n    if (!sectionStat.isDirectory()) continue;\n\n    const sectionName = SECTION_NAMES[section] || section;\n    const chapterDirs = readdirSync(sectionPath).sort();\n\n    for (const chapterDir of chapterDirs) {\n      const chapterPath = join(sectionPath, chapterDir);\n\n      if (!statSync(chapterPath).isDirectory()) continue;\n\n      // Check if this chapter has a playground\n      const playgroundPath = join(chapterPath, \"examples\", \"playground\");\n      const packagesPath = join(chapterPath, \"packages\");\n\n      if (!existsSync(playgroundPath)) continue;\n\n      // Read playground files\n      const playgroundFiles = readFilesRecursively(playgroundPath);\n\n      // Read packages files (with \"packages/\" prefix)\n      const packagesFiles = readFilesRecursively(packagesPath).map((f) => ({\n        path: `packages/${f.path}`,\n        content: f.content,\n      }));\n\n      // Console hook script to inject into index.html (minified for cleaner output)\n      const consoleHookScript = `<!-- chibivue playground: console hook -->\n    <script>!function(){var o={log:console.log,info:console.info,warn:console.warn,error:console.error};[\"log\",\"info\",\"warn\",\"error\"].forEach(function(e){console[e]=function(){o[e].apply(console,arguments);try{window.parent.postMessage({type:\"console\",level:e,args:Array.from(arguments).map(function(a){try{return\"object\"==typeof a?JSON.stringify(a):String(a)}catch(e){return String(a)}})},\"*\")}catch(e){}}});window.onerror=function(m,s,l){window.parent.postMessage({type:\"console\",level:\"error\",args:[m+\" at \"+s+\":\"+l]},\"*\")}}();</script>`;\n\n      // Check if this chapter uses vite-plugin-chibivue (needs extra dependencies)\n      const viteConfigFile = playgroundFiles.find((f) => f.path === \"vite.config.ts\");\n      const usesVitePlugin = viteConfigFile?.content.includes(\"vite-plugin-chibivue\");\n\n      // Fix vite.config.ts and inject console hook into index.html\n      const fixedPlaygroundFiles = playgroundFiles.map((f) => {\n        if (f.path === \"vite.config.ts\") {\n          // Replace relative paths to packages with the flat structure path\n          let fixedContent = f.content\n            .replace(\n              /path\\.resolve\\(dirname,\\s*[\"']\\.\\.\\/\\.\\.\\/packages[\"']\\)/g,\n              'path.resolve(dirname, \"packages\")',\n            )\n            // Fix import paths for @extensions\n            .replace(\n              /from\\s+[\"']\\.\\.\\/\\.\\.\\/packages\\/@extensions\\/([^\"']+)[\"']/g,\n              'from \"./packages/@extensions/$1\"',\n            )\n            .replace(\n              /import\\s+(\\w+)\\s+from\\s+[\"']\\.\\.\\/\\.\\.\\/packages\\/@extensions\\/([^\"']+)[\"']/g,\n              'import $1 from \"./packages/@extensions/$2\"',\n            );\n          return { ...f, content: fixedContent };\n        }\n        // Inject console hook into index.html\n        if (f.path === \"index.html\") {\n          const fixedContent = f.content.replace(/<head>/i, `<head>\\n${consoleHookScript}`);\n          return { ...f, content: fixedContent };\n        }\n        // Add extra dependencies to package.json if using vite-plugin-chibivue\n        if (f.path === \"package.json\" && usesVitePlugin) {\n          try {\n            const pkg = JSON.parse(f.content);\n            pkg.dependencies = pkg.dependencies || {};\n            pkg.dependencies[\"@babel/parser\"] = \"^7.28.6\";\n            pkg.dependencies[\"magic-string\"] = \"^0.30.21\";\n            pkg.dependencies[\"estree-walker\"] = \"^3.0.3\";\n            return { ...f, content: JSON.stringify(pkg, null, 2) + \"\\n\" };\n          } catch {\n            return f;\n          }\n        }\n        return f;\n      });\n\n      // Merge files\n      const allFiles = [...fixedPlaygroundFiles, ...packagesFiles];\n\n      if (allFiles.length === 0) continue;\n\n      const vueDocUrl = getVueDocUrl(chapterDir);\n      chapters.push({\n        id: `${section}/${chapterDir}`,\n        section: sectionName,\n        sectionOrder: section,\n        name: getChapterName(section, chapterDir),\n        files: allFiles,\n        bookUrl: getBookUrl(section, chapterDir),\n        ...(vueDocUrl && { vueDocUrl }),\n      });\n    }\n  }\n\n  return chapters;\n}\n\nfunction generateOutput(chapters: Chapter[]): string {\n  return `// This file is auto-generated by scripts/generate-chapters.ts\n// Do not edit manually\n\nimport type { Chapter } from \"./types\";\n\nexport const chapters: Chapter[] = ${JSON.stringify(chapters, null, 2)};\n`;\n}\n\n// Main\nconst chapters = loadChapters();\nconst output = generateOutput(chapters);\nwriteFileSync(OUTPUT_FILE, output);\nconsole.log(`Generated ${chapters.length} chapters to ${OUTPUT_FILE}`);\n"
  },
  {
    "path": "book/playground/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick, defineComponent, h } from \"vue\";\nimport { WebContainer } from \"@webcontainer/api\";\nimport loader from \"@monaco-editor/loader\";\nimport type * as Monaco from \"monaco-editor\";\nimport { chapters } from \"./chapters.generated\";\nimport type { Chapter } from \"./types\";\n\n// State\nconst selectedChapterId = ref(\"\");\nconst selectedFile = ref<string | undefined>(undefined);\nconst fileContents = ref<Map<string, string>>(new Map());\nconst originalContents = ref<Map<string, string>>(new Map());\nconst modifiedFiles = ref<Set<string>>(new Set());\nconst terminalOutput = ref<string[]>([]);\nconst consoleOutput = ref<{ type: string; message: string; timestamp: Date }[]>([]);\nconst activeTab = ref<'terminal' | 'console'>('terminal');\nconst previewUrl = ref(\"\");\nconst isLoading = ref(false);\nconst isBooting = ref(false);\nconst isInitializing = ref(true);\nconst editorContainer = ref<HTMLDivElement | null>(null);\nconst terminalContainer = ref<HTMLDivElement | null>(null);\nconst consoleContainer = ref<HTMLDivElement | null>(null);\nconst expandedDirs = ref<Set<string>>(new Set());\nconst sidebarWidth = ref(240);\nconst terminalHeight = ref(200);\nconst isResizingSidebar = ref(false);\nconst isResizingTerminal = ref(false);\n\n// Chapter selector state\nconst isSelectorOpen = ref(false);\nconst searchQuery = ref(\"\");\nconst searchInputRef = ref<HTMLInputElement | null>(null);\nconst selectorRef = ref<HTMLDivElement | null>(null);\nconst highlightedIndex = ref(-1);\n\nlet webcontainer: WebContainer | null = null;\nlet editor: Monaco.editor.IStandaloneCodeEditor | null = null;\nlet monaco: typeof Monaco | null = null;\n\n// Clean terminal output - strip TUI control sequences (keep color codes)\nfunction cleanTerminalOutput(text: string): string {\n  return text\n    // Remove cursor movement and screen clearing\n    .replace(/\\x1b\\[\\d*[ABCDEFGJKST]/g, '')\n    .replace(/\\x1b\\[\\d*;\\d*[Hf]/g, '')\n    .replace(/\\x1b\\[\\??\\d*[hl]/g, '')\n    .replace(/\\x1b\\]\\d*;[^\\x07]*\\x07/g, '') // OSC sequences\n    .replace(/\\x1b[78]/g, '') // Save/restore cursor\n    .replace(/\\r/g, '') // Carriage returns\n    .replace(/\\x07/g, '') // Bell\n    .trim();\n}\n\n// ANSI color code to CSS class mapping\nconst ANSI_COLORS: Record<number, string> = {\n  30: '#6e7681', // black (dim)\n  31: '#f85149', // red\n  32: '#3fb950', // green\n  33: '#d29922', // yellow\n  34: '#58a6ff', // blue\n  35: '#bc8cff', // magenta\n  36: '#39c5cf', // cyan\n  37: '#c9d1d9', // white\n  90: '#6e7681', // bright black (gray)\n  91: '#ff7b72', // bright red\n  92: '#56d364', // bright green\n  93: '#e3b341', // bright yellow\n  94: '#79c0ff', // bright blue\n  95: '#d2a8ff', // bright magenta\n  96: '#56d4dd', // bright cyan\n  97: '#ffffff', // bright white\n};\n\n// ANSI to HTML conversion with full color support\nfunction ansiToHtml(text: string): string {\n  const cleaned = cleanTerminalOutput(text);\n  if (!cleaned) return '';\n\n  let result = '';\n  let currentColor: string | null = null;\n  let i = 0;\n\n  while (i < cleaned.length) {\n    // Check for ANSI escape sequence\n    if (cleaned[i] === '\\x1b' && cleaned[i + 1] === '[') {\n      const endIndex = cleaned.slice(i).search(/m/);\n      if (endIndex !== -1) {\n        const codes = cleaned.slice(i + 2, i + endIndex).split(';').map(Number);\n\n        for (const code of codes) {\n          if (code === 0) {\n            // Reset\n            if (currentColor) {\n              result += '</span>';\n              currentColor = null;\n            }\n          } else if (code === 1) {\n            // Bold - ignore for now\n          } else if (ANSI_COLORS[code]) {\n            if (currentColor) {\n              result += '</span>';\n            }\n            currentColor = ANSI_COLORS[code];\n            result += `<span style=\"color:${currentColor}\">`;\n          }\n        }\n\n        i += endIndex + 1;\n        continue;\n      }\n    }\n\n    // Escape HTML special characters\n    if (cleaned[i] === '<') {\n      result += '&lt;';\n    } else if (cleaned[i] === '>') {\n      result += '&gt;';\n    } else if (cleaned[i] === '&') {\n      result += '&amp;';\n    } else {\n      result += cleaned[i];\n    }\n    i++;\n  }\n\n  if (currentColor) {\n    result += '</span>';\n  }\n\n  return result;\n}\n\n// Computed\nconst selectedChapter = computed(() => chapters.find((c) => c.id === selectedChapterId.value));\n\n// Filtered chapters based on search query\nconst filteredChapters = computed(() => {\n  const query = searchQuery.value.toLowerCase().trim();\n  if (!query) return chapters;\n  return chapters.filter(c =>\n    c.name.toLowerCase().includes(query) ||\n    c.section.toLowerCase().includes(query)\n  );\n});\n\nconst filteredGroupedChapters = computed(() => {\n  const groups: Record<string, Chapter[]> = {};\n  for (const chapter of filteredChapters.value) {\n    if (!groups[chapter.section]) {\n      groups[chapter.section] = [];\n    }\n    groups[chapter.section].push(chapter);\n  }\n  return groups;\n});\n\nconst flatFilteredChapters = computed(() => filteredChapters.value);\n\nconst fileTree = computed(() => {\n  if (!selectedChapter.value) return [];\n  const files = selectedChapter.value.files;\n  const tree: { name: string; path: string; isDirectory: boolean; children?: any[] }[] = [];\n\n  for (const file of files) {\n    const parts = file.path.split(\"/\");\n    let current = tree;\n\n    for (let i = 0; i < parts.length; i++) {\n      const part = parts[i];\n      const isLast = i === parts.length - 1;\n      const existing = current.find((item) => item.name === part);\n\n      if (existing) {\n        if (!isLast && existing.children) {\n          current = existing.children;\n        }\n      } else {\n        const newItem = {\n          name: part,\n          path: parts.slice(0, i + 1).join(\"/\"),\n          isDirectory: !isLast,\n          children: isLast ? undefined : [],\n        };\n        current.push(newItem);\n        if (!isLast) {\n          current = newItem.children!;\n        }\n      }\n    }\n  }\n\n  return tree;\n});\n\n// Methods\nasync function selectChapter(chapterId: string) {\n  if (selectedChapterId.value === chapterId) return;\n\n  selectedChapterId.value = chapterId;\n  selectedFile.value = undefined;\n  fileContents.value.clear();\n  originalContents.value.clear();\n  modifiedFiles.value.clear();\n  terminalOutput.value = [];\n  previewUrl.value = \"\";\n  expandedDirs.value.clear();\n\n  const chapter = chapters.find((c) => c.id === chapterId);\n  if (!chapter) return;\n\n  for (const file of chapter.files) {\n    originalContents.value.set(file.path, file.content);\n    fileContents.value.set(file.path, file.content);\n  }\n\n  for (const file of chapter.files) {\n    const firstDir = file.path.split(\"/\")[0];\n    if (firstDir !== file.path) {\n      expandedDirs.value.add(firstDir);\n    }\n  }\n\n  // Prefer main.ts or src/main.ts as the default file\n  const defaultFile = chapter.files.find((f) => f.path === \"src/main.ts\")\n    || chapter.files.find((f) => f.path === \"main.ts\")\n    || chapter.files.find((f) => f.path.endsWith(\"/main.ts\"))\n    || chapter.files.find((f) => f.path.endsWith(\".ts\") && !f.path.includes(\"/\"))\n    || chapter.files.find((f) => !f.path.includes(\"/\") || f.path.split(\"/\").length <= 2);\n  if (defaultFile) {\n    selectFile(defaultFile.path);\n  }\n}\n\nfunction toggleDir(path: string) {\n  if (expandedDirs.value.has(path)) {\n    expandedDirs.value.delete(path);\n  } else {\n    expandedDirs.value.add(path);\n  }\n}\n\nasync function selectFile(path: string) {\n  selectedFile.value = path;\n  await nextTick();\n  updateEditor();\n}\n\nfunction updateEditor() {\n  if (!editor || !monaco || !selectedFile.value) return;\n\n  const content = fileContents.value.get(selectedFile.value) || \"\";\n  const ext = selectedFile.value.split(\".\").pop() || \"\";\n\n  const languageMap: Record<string, string> = {\n    ts: \"typescript\",\n    js: \"javascript\",\n    vue: \"html\",\n    html: \"html\",\n    css: \"css\",\n    json: \"json\",\n    md: \"markdown\",\n  };\n\n  const language = languageMap[ext] || \"plaintext\";\n\n  editor.setValue(content);\n  monaco.editor.setModelLanguage(editor.getModel()!, language);\n}\n\nfunction onEditorChange(value: string) {\n  if (!selectedFile.value) return;\n\n  fileContents.value.set(selectedFile.value, value);\n\n  const original = originalContents.value.get(selectedFile.value);\n  if (value !== original) {\n    modifiedFiles.value.add(selectedFile.value);\n  } else {\n    modifiedFiles.value.delete(selectedFile.value);\n  }\n}\n\nfunction resetFile() {\n  if (!selectedFile.value) return;\n\n  const original = originalContents.value.get(selectedFile.value);\n  if (original !== undefined) {\n    fileContents.value.set(selectedFile.value, original);\n    modifiedFiles.value.delete(selectedFile.value);\n    updateEditor();\n  }\n}\n\nfunction resetAllFiles() {\n  if (!selectedChapter.value) return;\n\n  for (const file of selectedChapter.value.files) {\n    fileContents.value.set(file.path, file.content);\n  }\n  modifiedFiles.value.clear();\n  updateEditor();\n}\n\nasync function bootWebContainer() {\n  if (isBooting.value) return;\n\n  isBooting.value = true;\n  terminalOutput.value = [\"Booting WebContainer...\"];\n\n  try {\n    if (!webcontainer) {\n      webcontainer = await WebContainer.boot();\n    }\n\n    const files: Record<string, any> = {};\n    for (const [path, content] of fileContents.value) {\n      const parts = path.split(\"/\");\n      let current = files;\n\n      for (let i = 0; i < parts.length - 1; i++) {\n        const part = parts[i];\n        if (!current[part]) {\n          current[part] = { directory: {} };\n        }\n        current = current[part].directory;\n      }\n\n      current[parts[parts.length - 1]] = { file: { contents: content } };\n    }\n\n    await webcontainer.mount(files);\n    terminalOutput.value.push(\"Files mounted successfully\");\n\n    terminalOutput.value.push(\"Installing dependencies with pnpm...\");\n    const installProcess = await webcontainer.spawn(\"pnpm\", [\"install\", \"--prefer-offline\"]);\n\n    installProcess.output.pipeTo(\n      new WritableStream({\n        write(data) {\n          const cleaned = cleanTerminalOutput(data);\n          if (cleaned && !cleaned.includes('Progress:')) {\n            terminalOutput.value.push(cleaned);\n          }\n        },\n      }),\n    );\n\n    const installExitCode = await installProcess.exit;\n    if (installExitCode !== 0) {\n      throw new Error(`Install failed with exit code ${installExitCode}`);\n    }\n\n    terminalOutput.value.push(\"Starting dev server...\");\n    const devProcess = await webcontainer.spawn(\"pnpm\", [\"run\", \"dev\"]);\n\n    devProcess.output.pipeTo(\n      new WritableStream({\n        write(data) {\n          const cleaned = cleanTerminalOutput(data);\n          if (cleaned) {\n            terminalOutput.value.push(cleaned);\n          }\n        },\n      }),\n    );\n\n    webcontainer.on(\"server-ready\", (_port, url) => {\n      previewUrl.value = url;\n      terminalOutput.value.push(`Server ready at ${url}`);\n    });\n  } catch (e) {\n    terminalOutput.value.push(`Error: ${e}`);\n    console.error(e);\n  } finally {\n    isBooting.value = false;\n  }\n}\n\nasync function applyChanges() {\n  if (!webcontainer || modifiedFiles.value.size === 0) return;\n\n  isLoading.value = true;\n  terminalOutput.value.push(\"Applying changes...\");\n\n  try {\n    for (const path of modifiedFiles.value) {\n      const content = fileContents.value.get(path);\n      if (content !== undefined) {\n        await webcontainer.fs.writeFile(path, content);\n        terminalOutput.value.push(`Updated: ${path}`);\n      }\n    }\n    terminalOutput.value.push(\"Changes applied. HMR should trigger.\");\n  } catch (e) {\n    terminalOutput.value.push(`Error: ${e}`);\n  } finally {\n    isLoading.value = false;\n  }\n}\n\n// Resize handlers\nfunction startResizeSidebar(e: MouseEvent) {\n  isResizingSidebar.value = true;\n  document.addEventListener('mousemove', resizeSidebar);\n  document.addEventListener('mouseup', stopResizeSidebar);\n  e.preventDefault();\n}\n\nfunction resizeSidebar(e: MouseEvent) {\n  if (isResizingSidebar.value) {\n    sidebarWidth.value = Math.max(180, Math.min(400, e.clientX));\n  }\n}\n\nfunction stopResizeSidebar() {\n  isResizingSidebar.value = false;\n  document.removeEventListener('mousemove', resizeSidebar);\n  document.removeEventListener('mouseup', stopResizeSidebar);\n}\n\nfunction startResizeTerminal(e: MouseEvent) {\n  isResizingTerminal.value = true;\n  document.addEventListener('mousemove', resizeTerminal);\n  document.addEventListener('mouseup', stopResizeTerminal);\n  e.preventDefault();\n}\n\nfunction resizeTerminal(e: MouseEvent) {\n  if (isResizingTerminal.value) {\n    const containerRect = document.querySelector('.playground-main')?.getBoundingClientRect();\n    if (containerRect) {\n      terminalHeight.value = Math.max(100, Math.min(400, containerRect.bottom - e.clientY));\n    }\n  }\n}\n\nfunction stopResizeTerminal() {\n  isResizingTerminal.value = false;\n  document.removeEventListener('mousemove', resizeTerminal);\n  document.removeEventListener('mouseup', stopResizeTerminal);\n}\n\n// Chapter selector methods\nfunction openSelector() {\n  isSelectorOpen.value = true;\n  searchQuery.value = \"\";\n  highlightedIndex.value = -1;\n  nextTick(() => {\n    searchInputRef.value?.focus();\n  });\n}\n\nfunction closeSelector() {\n  isSelectorOpen.value = false;\n  searchQuery.value = \"\";\n  highlightedIndex.value = -1;\n}\n\nfunction handleSelectorSelect(chapterId: string) {\n  selectChapter(chapterId);\n  closeSelector();\n}\n\nfunction handleSelectorKeydown(e: KeyboardEvent) {\n  const chapters = flatFilteredChapters.value;\n  if (e.key === 'Escape') {\n    closeSelector();\n  } else if (e.key === 'ArrowDown') {\n    e.preventDefault();\n    highlightedIndex.value = Math.min(highlightedIndex.value + 1, chapters.length - 1);\n  } else if (e.key === 'ArrowUp') {\n    e.preventDefault();\n    highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0);\n  } else if (e.key === 'Enter' && highlightedIndex.value >= 0) {\n    e.preventDefault();\n    handleSelectorSelect(chapters[highlightedIndex.value].id);\n  }\n}\n\nfunction handleClickOutside(e: MouseEvent) {\n  if (selectorRef.value && !selectorRef.value.contains(e.target as Node)) {\n    closeSelector();\n  }\n}\n\n// FileTreeItem component with MDI icons\nconst FileTreeItem = defineComponent({\n  name: \"FileTreeItem\",\n  props: {\n    item: { type: Object, required: true },\n    selectedFile: { type: String, default: undefined },\n    modifiedFiles: { type: Set, required: true },\n    expandedDirs: { type: Set, required: true },\n    depth: { type: Number, default: 0 },\n  },\n  emits: [\"select\", \"toggle\"],\n  setup(props, { emit }) {\n    return () => {\n      const item = props.item as any;\n      const isSelected = props.selectedFile === item.path;\n      const isModified = (props.modifiedFiles as Set<string>).has(item.path);\n      const isExpanded = (props.expandedDirs as Set<string>).has(item.path);\n\n      // File type icons using MDI\n      const getFileIcon = (name: string): { icon: string; color: string } => {\n        if (name.endsWith('.vue')) return { icon: 'mdi-vuejs', color: '#42b883' };\n        if (name.endsWith('.ts') || name.endsWith('.tsx')) return { icon: 'mdi-language-typescript', color: '#3178c6' };\n        if (name.endsWith('.js') || name.endsWith('.jsx')) return { icon: 'mdi-language-javascript', color: '#f7df1e' };\n        if (name.endsWith('.json')) return { icon: 'mdi-code-json', color: '#cbcb41' };\n        if (name.endsWith('.css')) return { icon: 'mdi-language-css3', color: '#264de4' };\n        if (name.endsWith('.html')) return { icon: 'mdi-language-html5', color: '#e44d26' };\n        if (name.endsWith('.md')) return { icon: 'mdi-language-markdown', color: '#519aba' };\n        if (name === 'package.json') return { icon: 'mdi-nodejs', color: '#8bc500' };\n        if (name === 'vite.config.ts') return { icon: 'mdi-flash', color: '#646cff' };\n        if (name === 'tsconfig.json') return { icon: 'mdi-cog', color: '#3178c6' };\n        return { icon: 'mdi-file-outline', color: '#6e7681' };\n      };\n\n      // Folder icon based on name\n      const getFolderIcon = (name: string): { icon: string; color: string } => {\n        if (name === 'src') return { icon: 'mdi-folder-star', color: '#42b883' };\n        if (name === 'packages') return { icon: 'mdi-package-variant', color: '#c678dd' };\n        if (name === 'node_modules') return { icon: 'mdi-folder-cog', color: '#6e7681' };\n        if (name === 'dist') return { icon: 'mdi-folder-zip', color: '#f7df1e' };\n        return { icon: 'mdi-folder', color: '#90a4ae' };\n      };\n\n      if (item.isDirectory) {\n        const folderInfo = getFolderIcon(item.name);\n        const openIcon = folderInfo.icon.replace('mdi-folder', 'mdi-folder-open').replace('-star', '-star-outline');\n        return h(\"div\", { class: \"tree-directory\" }, [\n          h(\"div\", {\n            class: [\"tree-item\", \"directory\", { expanded: isExpanded }],\n            style: { paddingLeft: `${props.depth * 16 + 8}px` },\n            onClick: () => emit(\"toggle\", item.path),\n          }, [\n            h(\"span\", { class: [\"mdi\", isExpanded ? \"mdi-chevron-down\" : \"mdi-chevron-right\", \"tree-chevron\"] }),\n            h(\"span\", {\n              class: [\"mdi\", isExpanded ? openIcon : folderInfo.icon, \"tree-icon\"],\n              style: { color: folderInfo.color }\n            }),\n            h(\"span\", { class: \"item-name\" }, item.name),\n          ]),\n          isExpanded\n            ? h(\"div\", { class: \"tree-children\" },\n                (item.children || []).map((child: any) =>\n                  h(FileTreeItem, {\n                    item: child,\n                    selectedFile: props.selectedFile,\n                    modifiedFiles: props.modifiedFiles,\n                    expandedDirs: props.expandedDirs,\n                    depth: props.depth + 1,\n                    onSelect: (path: string) => emit(\"select\", path),\n                    onToggle: (path: string) => emit(\"toggle\", path),\n                  }),\n                )\n              )\n            : null,\n        ]);\n      }\n\n      const fileInfo = getFileIcon(item.name);\n      return h(\n        \"div\",\n        {\n          class: [\"tree-item\", \"file\", { selected: isSelected, modified: isModified }],\n          style: { paddingLeft: `${props.depth * 16 + 8}px` },\n          onClick: () => emit(\"select\", item.path),\n        },\n        [\n          h(\"span\", { class: \"tree-chevron-spacer\" }),\n          h(\"span\", {\n            class: [\"mdi\", fileInfo.icon, \"tree-icon\"],\n            style: { color: fileInfo.color }\n          }),\n          h(\"span\", { class: \"item-name\" }, item.name),\n          isModified ? h(\"span\", { class: \"modified-dot\" }) : null,\n        ],\n      );\n    };\n  },\n});\n\n// Initialize editor after DOM is ready\nasync function initEditor() {\n  if (!monaco || !editorContainer.value || editor) return;\n\n  // Configure TypeScript compiler options to be lenient with imports\n  monaco.languages.typescript.typescriptDefaults.setCompilerOptions({\n    target: monaco.languages.typescript.ScriptTarget.ESNext,\n    module: monaco.languages.typescript.ModuleKind.ESNext,\n    moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,\n    allowNonTsExtensions: true,\n    allowSyntheticDefaultImports: true,\n    esModuleInterop: true,\n    strict: false,\n    noEmit: true,\n    jsx: monaco.languages.typescript.JsxEmit.Preserve,\n  });\n\n  // Disable semantic validation to avoid import errors for unknown modules\n  monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({\n    noSemanticValidation: true,\n    noSyntaxValidation: false,\n  });\n\n  monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({\n    noSemanticValidation: true,\n    noSyntaxValidation: false,\n  });\n\n  editor = monaco.editor.create(editorContainer.value, {\n    value: \"\",\n    language: \"typescript\",\n    theme: \"vs-dark\",\n    automaticLayout: true,\n    minimap: { enabled: false },\n    fontSize: 13,\n    lineNumbers: \"on\",\n    scrollBeyondLastLine: false,\n    wordWrap: \"on\",\n    padding: { top: 12 },\n    fontFamily: \"'JetBrains Mono', 'Fira Code', 'Consolas', monospace\",\n    fontLigatures: true,\n    renderLineHighlight: 'gutter',\n    smoothScrolling: true,\n    cursorBlinking: 'smooth',\n    cursorSmoothCaretAnimation: 'on',\n  });\n\n  editor.onDidChangeModelContent(() => {\n    onEditorChange(editor!.getValue());\n  });\n\n  // Update editor with current file content\n  updateEditor();\n}\n\n// Console message handler\nfunction handleConsoleMessage(event: MessageEvent) {\n  if (event.data?.type === 'console') {\n    consoleOutput.value.push({\n      type: event.data.level || 'log',\n      message: event.data.args?.map((a: any) => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') || '',\n      timestamp: new Date(),\n    });\n  }\n}\n\n// Lifecycle\nonMounted(async () => {\n  monaco = await loader.init();\n\n  // Listen for console messages from iframe\n  window.addEventListener('message', handleConsoleMessage);\n\n  if (chapters.length > 0) {\n    await selectChapter(chapters[0].id);\n  }\n\n  isInitializing.value = false;\n\n  // Wait for DOM to render, then init editor\n  await nextTick();\n  await initEditor();\n});\n\nonUnmounted(() => {\n  editor?.dispose();\n  webcontainer?.teardown();\n  window.removeEventListener('message', handleConsoleMessage);\n  document.removeEventListener('click', handleClickOutside);\n});\n\n// Watch for selector open state to add/remove click outside listener\nwatch(isSelectorOpen, (isOpen) => {\n  if (isOpen) {\n    setTimeout(() => {\n      document.addEventListener('click', handleClickOutside);\n    }, 0);\n  } else {\n    document.removeEventListener('click', handleClickOutside);\n  }\n});\n\nwatch(terminalOutput, async () => {\n  await nextTick();\n  if (terminalContainer.value) {\n    terminalContainer.value.scrollTop = terminalContainer.value.scrollHeight;\n  }\n});\n\nwatch(consoleOutput, async () => {\n  await nextTick();\n  if (consoleContainer.value) {\n    consoleContainer.value.scrollTop = consoleContainer.value.scrollHeight;\n  }\n}, { deep: true });\n</script>\n\n<template>\n  <!-- Loading Overlay -->\n  <div v-if=\"isInitializing\" class=\"loading-overlay\">\n    <div class=\"loading-content\">\n      <img src=\"/kawaiko.png\" alt=\"chibivue\" class=\"loading-logo\" />\n      <div class=\"loading-spinner-container\">\n        <div class=\"loading-spinner\"></div>\n      </div>\n      <p class=\"loading-text\">Initializing Playground...</p>\n    </div>\n  </div>\n\n  <div v-else class=\"playground\">\n    <!-- Header -->\n    <header class=\"playground-header\">\n      <div class=\"header-brand\">\n        <img src=\"/kawaiko.png\" alt=\"chibivue\" class=\"brand-logo\" />\n        <div class=\"brand-text\">\n          <h1 class=\"brand-title\">chibivue</h1>\n          <span class=\"brand-subtitle\">Playground</span>\n        </div>\n      </div>\n\n      <div class=\"header-center\">\n        <!-- Custom Chapter Selector -->\n        <div ref=\"selectorRef\" class=\"chapter-selector-custom\">\n          <button class=\"selector-trigger\" @click=\"openSelector\">\n            <span class=\"mdi mdi-book-multiple selector-icon\"></span>\n            <span class=\"selector-text\">\n              {{ selectedChapter ? selectedChapter.name : 'Select a chapter...' }}\n            </span>\n            <span class=\"mdi selector-arrow\" :class=\"isSelectorOpen ? 'mdi-chevron-up' : 'mdi-chevron-down'\"></span>\n          </button>\n\n          <!-- Dropdown -->\n          <Teleport to=\"body\">\n            <div v-if=\"isSelectorOpen\" class=\"selector-dropdown\" @keydown=\"handleSelectorKeydown\">\n              <div class=\"selector-header\">\n                <div class=\"search-wrapper\">\n                  <span class=\"mdi mdi-magnify search-icon\"></span>\n                  <input\n                    ref=\"searchInputRef\"\n                    v-model=\"searchQuery\"\n                    type=\"text\"\n                    class=\"search-input\"\n                    placeholder=\"Search chapters...\"\n                    @keydown=\"handleSelectorKeydown\"\n                  />\n                  <span v-if=\"searchQuery\" class=\"mdi mdi-close search-clear\" @click=\"searchQuery = ''\"></span>\n                </div>\n              </div>\n\n              <div class=\"selector-content\">\n                <template v-if=\"Object.keys(filteredGroupedChapters).length > 0\">\n                  <div\n                    v-for=\"(chapterList, section) in filteredGroupedChapters\"\n                    :key=\"section\"\n                    class=\"selector-group\"\n                  >\n                    <div class=\"group-header\">{{ section }}</div>\n                    <div\n                      v-for=\"chapter in chapterList\"\n                      :key=\"chapter.id\"\n                      class=\"selector-item\"\n                      :class=\"{\n                        selected: chapter.id === selectedChapterId,\n                        highlighted: flatFilteredChapters.indexOf(chapter) === highlightedIndex\n                      }\"\n                      @click=\"handleSelectorSelect(chapter.id)\"\n                      @mouseenter=\"highlightedIndex = flatFilteredChapters.indexOf(chapter)\"\n                    >\n                      <span class=\"mdi mdi-check item-check\" v-if=\"chapter.id === selectedChapterId\"></span>\n                      <span class=\"item-name\">{{ chapter.name }}</span>\n                    </div>\n                  </div>\n                </template>\n                <div v-else class=\"selector-empty\">\n                  <span>No chapters found</span>\n                </div>\n              </div>\n\n              <div class=\"selector-footer\">\n                <span class=\"footer-hint\">\n                  <kbd>↑</kbd><kbd>↓</kbd> Navigate\n                  <kbd>Enter</kbd> Select\n                  <kbd>Esc</kbd> Close\n                </span>\n                <span class=\"chapter-count\">{{ filteredChapters.length }} chapters</span>\n              </div>\n            </div>\n          </Teleport>\n        </div>\n\n        <div v-if=\"selectedChapter\" class=\"chapter-links\">\n          <a :href=\"selectedChapter.bookUrl\" target=\"_blank\" class=\"link-btn link-book\">\n            <img src=\"/kawaiko.png\" alt=\"\" class=\"link-icon-img\" />\n            Book\n          </a>\n          <a v-if=\"selectedChapter.vueDocUrl\" :href=\"selectedChapter.vueDocUrl\" target=\"_blank\" class=\"link-btn link-vue\">\n            <span class=\"mdi mdi-vuejs link-icon\"></span>\n            Vue Docs\n          </a>\n        </div>\n      </div>\n\n      <div class=\"header-actions\">\n        <button @click=\"bootWebContainer\" :disabled=\"!selectedChapterId || isBooting\" class=\"btn btn-run\">\n          <span v-if=\"isBooting\" class=\"btn-spinner\"></span>\n          <span v-else class=\"mdi mdi-play btn-icon\"></span>\n          {{ isBooting ? \"Starting...\" : previewUrl ? \"Restart\" : \"Run\" }}\n        </button>\n        <button @click=\"applyChanges\" :disabled=\"!previewUrl || modifiedFiles.size === 0 || isLoading\" class=\"btn btn-apply\">\n          <span class=\"mdi mdi-refresh btn-icon\"></span>\n          Apply\n        </button>\n        <button @click=\"resetAllFiles\" :disabled=\"!selectedChapterId || modifiedFiles.size === 0\" class=\"btn btn-reset\">\n          <span class=\"mdi mdi-restore btn-icon\"></span>\n          Reset\n        </button>\n      </div>\n    </header>\n\n    <!-- Main content -->\n    <div class=\"playground-main\">\n      <div class=\"main-panels\">\n        <!-- File explorer -->\n        <aside class=\"file-explorer\" :style=\"{ width: `${sidebarWidth}px` }\">\n          <div class=\"panel-header\">\n            <span class=\"panel-title\">Explorer</span>\n            <span class=\"file-count\" v-if=\"selectedChapter\">{{ selectedChapter.files.length }} files</span>\n          </div>\n          <div class=\"file-tree\">\n            <template v-for=\"item in fileTree\" :key=\"item.path\">\n              <FileTreeItem\n                :item=\"item\"\n                :selected-file=\"selectedFile\"\n                :modified-files=\"modifiedFiles\"\n                :expanded-dirs=\"expandedDirs\"\n                @select=\"selectFile\"\n                @toggle=\"toggleDir\"\n              />\n            </template>\n          </div>\n        </aside>\n\n        <!-- Sidebar resize handle -->\n        <div class=\"resize-handle-vertical\" @mousedown=\"startResizeSidebar\"></div>\n\n        <!-- Editor -->\n        <main class=\"editor-panel\">\n          <div class=\"panel-header editor-header\">\n            <div class=\"tab-bar\" v-if=\"selectedFile\">\n              <div class=\"tab active\">\n                <span class=\"tab-name\">{{ selectedFile.split('/').pop() }}</span>\n                <span v-if=\"modifiedFiles.has(selectedFile)\" class=\"tab-modified\"></span>\n              </div>\n            </div>\n            <div v-else class=\"tab-bar-empty\">No file selected</div>\n            <button\n              v-if=\"selectedFile && modifiedFiles.has(selectedFile)\"\n              @click=\"resetFile\"\n              class=\"btn btn-tiny\"\n            >\n              Reset File\n            </button>\n          </div>\n          <div ref=\"editorContainer\" class=\"editor-container\"></div>\n        </main>\n\n        <!-- Preview -->\n        <aside class=\"preview-panel\">\n          <div class=\"panel-header\">\n            <span class=\"panel-title\">Preview</span>\n            <span v-if=\"previewUrl\" class=\"preview-url\">{{ previewUrl }}</span>\n          </div>\n          <div class=\"preview-content\">\n            <iframe v-if=\"previewUrl\" :src=\"previewUrl\" class=\"preview-frame\"></iframe>\n            <div v-else class=\"preview-placeholder\">\n              <img src=\"/kawaiko.png\" alt=\"chibivue\" class=\"placeholder-logo\" />\n              <p class=\"placeholder-text\">Select a chapter and click <strong>Run</strong> to start</p>\n            </div>\n          </div>\n        </aside>\n      </div>\n\n      <!-- Terminal resize handle -->\n      <div class=\"resize-handle-horizontal\" @mousedown=\"startResizeTerminal\"></div>\n\n      <!-- Terminal & Console Panel -->\n      <div class=\"terminal-panel\" :style=\"{ height: `${terminalHeight}px` }\">\n        <div class=\"panel-header terminal-header\">\n          <div class=\"output-tabs\">\n            <button\n              :class=\"['output-tab', { active: activeTab === 'terminal' }]\"\n              @click=\"activeTab = 'terminal'\"\n            >\n              Terminal\n            </button>\n            <button\n              :class=\"['output-tab', { active: activeTab === 'console' }]\"\n              @click=\"activeTab = 'console'\"\n            >\n              Console\n              <span v-if=\"consoleOutput.length > 0\" class=\"console-badge\">{{ consoleOutput.length }}</span>\n            </button>\n          </div>\n          <div class=\"terminal-actions\">\n            <button\n              v-if=\"activeTab === 'terminal'\"\n              @click=\"terminalOutput = []\"\n              class=\"terminal-btn\"\n              title=\"Clear\"\n            >\n              Clear\n            </button>\n            <button\n              v-else\n              @click=\"consoleOutput = []\"\n              class=\"terminal-btn\"\n              title=\"Clear\"\n            >\n              Clear\n            </button>\n          </div>\n        </div>\n\n        <!-- Terminal Output -->\n        <div v-show=\"activeTab === 'terminal'\" ref=\"terminalContainer\" class=\"terminal-output\">\n          <div\n            v-for=\"(line, index) in terminalOutput\"\n            :key=\"index\"\n            class=\"terminal-line\"\n            v-html=\"ansiToHtml(line)\"\n          ></div>\n          <div v-if=\"terminalOutput.length === 0\" class=\"terminal-empty\">\n            Ready. Click Run to start the dev server...\n          </div>\n        </div>\n\n        <!-- Console Output -->\n        <div v-show=\"activeTab === 'console'\" ref=\"consoleContainer\" class=\"console-output\">\n          <div\n            v-for=\"(entry, index) in consoleOutput\"\n            :key=\"index\"\n            :class=\"['console-entry', `console-${entry.type}`]\"\n          >\n            <span class=\"console-time\">{{ entry.timestamp.toLocaleTimeString() }}</span>\n            <span class=\"console-type\">[{{ entry.type }}]</span>\n            <span class=\"console-message\">{{ entry.message }}</span>\n          </div>\n          <div v-if=\"consoleOutput.length === 0\" class=\"terminal-empty\">\n            Console output will appear here when the app runs...\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n/* Loading Overlay */\n.loading-overlay {\n  position: fixed;\n  inset: 0;\n  background: linear-gradient(135deg, #0a0f18 0%, #0f1724 50%, #0a1628 100%);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 1000;\n}\n\n.loading-overlay::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  background:\n    radial-gradient(ellipse 80% 50% at 50% 0%, rgba(44, 201, 168, 0.08) 0%, transparent 50%),\n    radial-gradient(ellipse 60% 40% at 80% 100%, rgba(44, 201, 168, 0.05) 0%, transparent 50%);\n  pointer-events: none;\n}\n\n.loading-content {\n  text-align: center;\n  position: relative;\n  z-index: 1;\n}\n\n.loading-logo {\n  width: 140px;\n  height: 140px;\n  margin-bottom: 32px;\n  animation: float 3s ease-in-out infinite;\n  filter: drop-shadow(0 20px 40px rgba(44, 201, 168, 0.2));\n}\n\n@keyframes float {\n  0%, 100% { transform: translateY(0) scale(1); }\n  50% { transform: translateY(-12px) scale(1.02); }\n}\n\n.loading-spinner-container {\n  margin: 24px auto;\n  position: relative;\n}\n\n.loading-spinner {\n  width: 48px;\n  height: 48px;\n  border: 3px solid rgba(44, 201, 168, 0.1);\n  border-top-color: var(--accent);\n  border-radius: 50%;\n  animation: spin 0.8s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n  margin: 0 auto;\n  box-shadow: 0 0 20px rgba(44, 201, 168, 0.2);\n}\n\n@keyframes spin {\n  to { transform: rotate(360deg); }\n}\n\n.loading-text {\n  color: var(--text-muted);\n  font-size: 13px;\n  margin-top: 20px;\n  letter-spacing: 0.5px;\n  font-weight: 500;\n}\n\n/* Main Layout */\n.playground {\n  display: flex;\n  flex-direction: column;\n  height: 100vh;\n  background: #0a0f18;\n  overflow: hidden;\n  position: relative;\n}\n\n.playground::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  background:\n    radial-gradient(ellipse 100% 60% at 0% 0%, rgba(44, 201, 168, 0.04) 0%, transparent 50%),\n    radial-gradient(ellipse 80% 50% at 100% 100%, rgba(26, 39, 68, 0.5) 0%, transparent 50%);\n  pointer-events: none;\n}\n\n/* Header */\n.playground-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0 20px;\n  height: 56px;\n  background: linear-gradient(180deg, rgba(15, 23, 36, 0.95) 0%, rgba(10, 15, 24, 0.9) 100%);\n  backdrop-filter: blur(16px);\n  border-bottom: 1px solid rgba(44, 201, 168, 0.08);\n  flex-shrink: 0;\n  position: relative;\n  z-index: 100;\n}\n\n.playground-header::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  height: 1px;\n  background: linear-gradient(90deg, transparent 10%, rgba(44, 201, 168, 0.15) 50%, transparent 90%);\n}\n\n.playground-header::after {\n  content: '';\n  position: absolute;\n  bottom: -1px;\n  left: 0;\n  right: 0;\n  height: 1px;\n  background: linear-gradient(90deg, transparent 0%, var(--accent) 50%, transparent 100%);\n  opacity: 0.2;\n}\n\n.header-brand {\n  display: flex;\n  align-items: center;\n  gap: 14px;\n}\n\n.brand-logo {\n  width: 40px;\n  height: 40px;\n  transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n  filter: drop-shadow(0 2px 8px rgba(44, 201, 168, 0.3));\n}\n\n.brand-logo:hover {\n  transform: scale(1.15) rotate(8deg);\n  filter: drop-shadow(0 4px 12px rgba(44, 201, 168, 0.5));\n}\n\n.brand-text {\n  display: flex;\n  flex-direction: column;\n}\n\n.brand-title {\n  font-size: 18px;\n  font-weight: 800;\n  background: linear-gradient(135deg, var(--c-mint-300) 0%, var(--c-mint-500) 100%);\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  background-clip: text;\n  margin: 0;\n  line-height: 1.2;\n  letter-spacing: -0.5px;\n}\n\n.brand-subtitle {\n  font-size: 10px;\n  color: var(--text-muted);\n  letter-spacing: 1.5px;\n  text-transform: uppercase;\n  font-weight: 500;\n}\n\n.header-center {\n  display: flex;\n  align-items: center;\n  gap: 16px;\n}\n\n/* Custom Chapter Selector */\n.chapter-selector-custom {\n  position: relative;\n}\n\n.selector-trigger {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 18px;\n  background: rgba(27, 38, 55, 0.6);\n  backdrop-filter: blur(8px);\n  border: 1px solid rgba(44, 201, 168, 0.15);\n  border-radius: 12px;\n  color: var(--text-primary);\n  min-width: 300px;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05);\n}\n\n.selector-trigger:hover {\n  border-color: var(--c-mint-500);\n  background: rgba(36, 51, 82, 0.8);\n  box-shadow: 0 4px 16px rgba(44, 201, 168, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.08);\n  transform: translateY(-1px);\n}\n\n.selector-icon {\n  font-size: 18px;\n  color: var(--c-mint-400);\n}\n\n.selector-text {\n  flex: 1;\n  text-align: left;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.selector-arrow {\n  font-size: 18px;\n  color: var(--text-muted);\n  transition: transform 0.2s;\n}\n\n/* Selector Dropdown - uses Teleport so needs :global or in global CSS */\n.chapter-links {\n  display: flex;\n  gap: 8px;\n}\n\n.link-btn {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 8px 14px;\n  border-radius: 8px;\n  font-size: 12px;\n  font-weight: 600;\n  text-decoration: none;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n  border: 1px solid transparent;\n}\n\n.link-book {\n  background: linear-gradient(135deg, rgba(44, 201, 168, 0.15) 0%, rgba(44, 201, 168, 0.08) 100%);\n  color: var(--c-mint-300);\n  border-color: rgba(44, 201, 168, 0.2);\n}\n\n.link-book:hover {\n  background: linear-gradient(135deg, var(--c-mint-500) 0%, var(--c-mint-600) 100%);\n  color: white;\n  transform: translateY(-1px);\n  box-shadow: 0 4px 12px rgba(44, 201, 168, 0.3);\n}\n\n.link-vue {\n  background: linear-gradient(135deg, rgba(66, 184, 131, 0.15) 0%, rgba(66, 184, 131, 0.08) 100%);\n  color: #5fd9a4;\n  border-color: rgba(66, 184, 131, 0.2);\n}\n\n.link-vue:hover {\n  background: linear-gradient(135deg, #42b883 0%, #35a070 100%);\n  color: white;\n  transform: translateY(-1px);\n  box-shadow: 0 4px 12px rgba(66, 184, 131, 0.3);\n}\n\n.link-icon {\n  font-size: 16px;\n}\n\n.link-icon-img {\n  width: 16px;\n  height: 16px;\n  object-fit: contain;\n}\n\n/* Actions */\n.header-actions {\n  display: flex;\n  gap: 10px;\n}\n\n.btn {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 10px 20px;\n  border: none;\n  border-radius: 10px;\n  font-size: 13px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n  position: relative;\n  overflow: hidden;\n}\n\n.btn::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 50%);\n  opacity: 0;\n  transition: opacity 0.2s;\n}\n\n.btn:hover::before {\n  opacity: 1;\n}\n\n.btn:disabled {\n  opacity: 0.35;\n  cursor: not-allowed;\n  transform: none !important;\n}\n\n.btn-icon {\n  font-size: 18px;\n}\n\n.btn-run {\n  background: linear-gradient(135deg, var(--c-mint-400) 0%, var(--c-mint-600) 100%);\n  color: white;\n  box-shadow: 0 4px 15px rgba(44, 201, 168, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2);\n  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n\n.btn-run:hover:not(:disabled) {\n  background: linear-gradient(135deg, var(--c-mint-300) 0%, var(--c-mint-500) 100%);\n  transform: translateY(-2px);\n  box-shadow: 0 6px 20px rgba(44, 201, 168, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n\n.btn-run:active:not(:disabled) {\n  transform: translateY(0);\n  box-shadow: 0 2px 8px rgba(44, 201, 168, 0.3);\n}\n\n.btn-apply {\n  background: rgba(36, 51, 82, 0.8);\n  color: var(--text-primary);\n  border: 1px solid rgba(44, 201, 168, 0.2);\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n}\n\n.btn-apply:hover:not(:disabled) {\n  background: rgba(46, 63, 96, 0.9);\n  border-color: var(--c-mint-500);\n  transform: translateY(-1px);\n  box-shadow: 0 4px 12px rgba(44, 201, 168, 0.2);\n}\n\n.btn-reset {\n  background: rgba(36, 51, 82, 0.5);\n  color: var(--text-secondary);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n}\n\n.btn-reset:hover:not(:disabled) {\n  background: rgba(255, 107, 107, 0.15);\n  color: #ff8a8a;\n  border-color: rgba(255, 107, 107, 0.4);\n  transform: translateY(-1px);\n}\n\n.btn-spinner {\n  width: 14px;\n  height: 14px;\n  border: 2px solid rgba(255,255,255,0.3);\n  border-top-color: white;\n  border-radius: 50%;\n  animation: spin 0.8s linear infinite;\n}\n\n.btn-tiny {\n  padding: 5px 12px;\n  font-size: 11px;\n  background: rgba(27, 38, 55, 0.8);\n  color: var(--text-secondary);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 6px;\n  font-weight: 500;\n}\n\n.btn-tiny:hover {\n  background: rgba(36, 51, 82, 0.9);\n  color: var(--text-primary);\n  border-color: rgba(44, 201, 168, 0.3);\n}\n\n/* Main Content */\n.playground-main {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n}\n\n.main-panels {\n  flex: 1;\n  display: flex;\n  overflow: hidden;\n  min-height: 0;\n}\n\n/* Resize Handles */\n.resize-handle-vertical {\n  width: 6px;\n  background: transparent;\n  cursor: col-resize;\n  position: relative;\n  z-index: 10;\n  flex-shrink: 0;\n}\n\n.resize-handle-vertical::after {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 1px;\n  width: 2px;\n  height: 100%;\n  background: var(--border);\n  transition: background 0.2s;\n}\n\n.resize-handle-vertical:hover::after,\n.resize-handle-vertical:active::after {\n  background: var(--accent);\n}\n\n.resize-handle-horizontal {\n  height: 4px;\n  background: transparent;\n  cursor: row-resize;\n  position: relative;\n  margin: -2px 0;\n  z-index: 10;\n}\n\n.resize-handle-horizontal::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 1px;\n  width: 100%;\n  height: 2px;\n  background: var(--border);\n  transition: background 0.2s;\n}\n\n.resize-handle-horizontal:hover::after,\n.resize-handle-horizontal:active::after {\n  background: var(--accent);\n}\n\n/* Panel Header */\n.panel-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 10px 14px;\n  background: linear-gradient(180deg, rgba(15, 23, 36, 0.98) 0%, rgba(10, 15, 24, 0.95) 100%);\n  border-bottom: 1px solid rgba(44, 201, 168, 0.06);\n  min-height: 40px;\n  backdrop-filter: blur(8px);\n}\n\n.panel-title {\n  font-weight: 600;\n  font-size: 11px;\n  text-transform: uppercase;\n  letter-spacing: 0.8px;\n  color: var(--text-muted);\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.panel-title::before {\n  content: '';\n  width: 2px;\n  height: 14px;\n  background: linear-gradient(180deg, var(--c-mint-400) 0%, var(--c-mint-600) 100%);\n  border-radius: 1px;\n}\n\n/* File Explorer */\n.file-explorer {\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  background: linear-gradient(180deg, rgba(12, 18, 28, 0.98) 0%, rgba(8, 12, 20, 0.95) 100%);\n  border-right: 1px solid rgba(255, 255, 255, 0.04);\n  flex-shrink: 0;\n}\n\n.file-count {\n  font-size: 10px;\n  color: var(--c-mint-400);\n  background: rgba(44, 201, 168, 0.1);\n  padding: 2px 8px;\n  border-radius: 8px;\n  font-weight: 600;\n  border: 1px solid rgba(44, 201, 168, 0.15);\n}\n\n.file-tree {\n  flex: 1;\n  overflow: auto;\n  padding: 8px 6px;\n}\n\n:deep(.tree-directory) {\n  /* Directory container */\n}\n\n:deep(.tree-children) {\n  /* Children container */\n}\n\n:deep(.tree-item) {\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  padding: 3px 8px;\n  cursor: pointer;\n  font-size: 13px;\n  color: var(--text-secondary);\n  transition: all 0.12s ease;\n  user-select: none;\n  border-radius: 4px;\n  margin: 1px 4px;\n  position: relative;\n}\n\n:deep(.tree-item:hover) {\n  background: rgba(255, 255, 255, 0.04);\n  color: var(--text-primary);\n}\n\n:deep(.tree-item.directory) {\n  font-weight: 500;\n  color: var(--text-primary);\n}\n\n:deep(.tree-item.directory:hover) {\n  background: rgba(255, 255, 255, 0.05);\n}\n\n:deep(.tree-item.file) {\n  font-weight: 400;\n}\n\n:deep(.tree-item.selected) {\n  background: rgba(44, 201, 168, 0.12);\n  color: var(--text-primary);\n}\n\n:deep(.tree-item.selected::before) {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 2px;\n  bottom: 2px;\n  width: 2px;\n  background: var(--c-mint-400);\n  border-radius: 0 1px 1px 0;\n}\n\n:deep(.tree-item.modified .item-name) {\n  color: var(--c-duck-yellow);\n}\n\n/* MDI Tree Icons */\n:deep(.tree-chevron) {\n  font-size: 18px;\n  width: 18px;\n  color: var(--text-muted);\n  transition: color 0.15s ease;\n  flex-shrink: 0;\n}\n\n:deep(.tree-item.directory:hover .tree-chevron) {\n  color: var(--text-secondary);\n}\n\n:deep(.tree-chevron-spacer) {\n  width: 18px;\n  flex-shrink: 0;\n}\n\n:deep(.tree-icon) {\n  font-size: 18px;\n  width: 18px;\n  flex-shrink: 0;\n  transition: transform 0.15s ease;\n}\n\n:deep(.tree-item:hover .tree-icon) {\n  transform: scale(1.1);\n}\n\n:deep(.item-name) {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  line-height: 1.4;\n  margin-left: 4px;\n}\n\n:deep(.modified-dot) {\n  width: 6px;\n  height: 6px;\n  background: var(--c-duck-yellow);\n  border-radius: 50%;\n  flex-shrink: 0;\n  box-shadow: 0 0 4px var(--c-duck-yellow);\n}\n\n/* Editor Panel */\n.editor-panel {\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  border-right: 1px solid rgba(255, 255, 255, 0.03);\n  flex: 1;\n  min-width: 0;\n  background: #0c1018;\n}\n\n.editor-header {\n  padding: 0;\n  background: linear-gradient(180deg, rgba(12, 16, 24, 0.98) 0%, rgba(10, 14, 22, 0.95) 100%);\n  border-bottom: 1px solid rgba(255, 255, 255, 0.04);\n}\n\n.tab-bar {\n  display: flex;\n  padding: 0 8px;\n  gap: 2px;\n  align-items: flex-end;\n}\n\n.tab {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 10px 16px;\n  font-size: 12px;\n  color: var(--text-muted);\n  border-bottom: 2px solid transparent;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n  border-radius: 6px 6px 0 0;\n  margin-bottom: -1px;\n  background: transparent;\n}\n\n.tab:hover {\n  color: var(--text-secondary);\n  background: rgba(255, 255, 255, 0.02);\n}\n\n.tab.active {\n  color: var(--text-primary);\n  border-bottom-color: var(--c-mint-400);\n  background: rgba(44, 201, 168, 0.05);\n}\n\n.tab-name {\n  font-weight: 500;\n  letter-spacing: -0.2px;\n}\n\n.tab-modified {\n  width: 7px;\n  height: 7px;\n  background: var(--c-duck-yellow);\n  border-radius: 50%;\n  box-shadow: 0 0 6px rgba(244, 211, 94, 0.6);\n  animation: pulse 2s ease-in-out infinite;\n}\n\n@keyframes pulse {\n  0%, 100% { opacity: 1; transform: scale(1); }\n  50% { opacity: 0.6; transform: scale(0.85); }\n}\n\n.tab-bar-empty {\n  padding: 10px 16px;\n  color: var(--text-muted);\n  font-size: 12px;\n  font-style: italic;\n}\n\n.editor-container {\n  flex: 1;\n  min-height: 0;\n}\n\n/* Preview Panel */\n.preview-panel {\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  background: linear-gradient(180deg, rgba(10, 15, 24, 0.98) 0%, rgba(8, 12, 20, 0.95) 100%);\n  flex: 1;\n  min-width: 0;\n}\n\n.preview-url {\n  font-size: 10px;\n  color: var(--c-mint-400);\n  font-family: 'JetBrains Mono', monospace;\n  background: rgba(44, 201, 168, 0.08);\n  padding: 3px 10px;\n  border-radius: 6px;\n  max-width: 180px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  border: 1px solid rgba(44, 201, 168, 0.12);\n}\n\n.preview-content {\n  flex: 1;\n  min-height: 0;\n  display: flex;\n  flex-direction: column;\n  margin: 10px;\n  border-radius: 10px;\n  overflow: hidden;\n  background: rgba(0, 0, 0, 0.2);\n  box-shadow:\n    0 4px 24px rgba(0, 0, 0, 0.4),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.03);\n}\n\n.preview-frame {\n  flex: 1;\n  border: none;\n  background: white;\n}\n\n.preview-placeholder {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 24px;\n  padding: 40px;\n  text-align: center;\n  background:\n    radial-gradient(ellipse 50% 40% at 50% 50%, rgba(44, 201, 168, 0.04) 0%, transparent 70%);\n}\n\n.placeholder-logo {\n  width: 100px;\n  height: 100px;\n  opacity: 0.85;\n  animation: float 3s ease-in-out infinite;\n  filter: drop-shadow(0 12px 24px rgba(44, 201, 168, 0.25));\n}\n\n.placeholder-text {\n  color: var(--text-muted);\n  font-size: 13px;\n  max-width: 200px;\n  line-height: 1.7;\n}\n\n.placeholder-text strong {\n  color: var(--c-mint-400);\n  font-weight: 600;\n  padding: 2px 10px;\n  background: rgba(44, 201, 168, 0.12);\n  border-radius: 4px;\n  border: 1px solid rgba(44, 201, 168, 0.15);\n}\n\n/* Terminal */\n.terminal-panel {\n  display: flex;\n  flex-direction: column;\n  background: #090c10;\n  border-top: 1px solid rgba(255, 255, 255, 0.04);\n  flex-shrink: 0;\n  position: relative;\n}\n\n.terminal-panel::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  height: 1px;\n  background: linear-gradient(90deg, transparent 10%, rgba(44, 201, 168, 0.15) 50%, transparent 90%);\n}\n\n.terminal-header {\n  background: linear-gradient(180deg, rgba(12, 16, 24, 0.98) 0%, rgba(9, 12, 16, 0.95) 100%);\n  border-bottom: 1px solid rgba(255, 255, 255, 0.03);\n}\n\n.terminal-actions {\n  display: flex;\n  gap: 6px;\n}\n\n.terminal-btn {\n  padding: 4px 10px;\n  background: rgba(255, 255, 255, 0.03);\n  border: 1px solid rgba(255, 255, 255, 0.06);\n  border-radius: 5px;\n  color: var(--text-muted);\n  font-size: 11px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.terminal-btn:hover {\n  background: rgba(44, 201, 168, 0.1);\n  border-color: rgba(44, 201, 168, 0.2);\n  color: var(--c-mint-400);\n}\n\n.terminal-output {\n  flex: 1;\n  overflow: auto;\n  padding: 12px 16px;\n  font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;\n  font-size: 11px;\n  line-height: 1.7;\n  color: #c9d1d9;\n  background: rgba(9, 12, 16, 0.95);\n}\n\n.terminal-line {\n  white-space: pre-wrap;\n  word-break: break-all;\n  padding: 1px 0;\n}\n\n.terminal-empty {\n  color: #6e7681;\n  font-style: italic;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.terminal-empty::before {\n  content: '●';\n  color: var(--c-mint-500);\n  animation: blink 1.5s ease-in-out infinite;\n}\n\n@keyframes blink {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.3; }\n}\n\n.terminal-prompt {\n  color: var(--c-mint-400);\n  font-weight: 600;\n}\n\n/* Output Tabs */\n.output-tabs {\n  display: flex;\n  gap: 2px;\n}\n\n.output-tab {\n  padding: 8px 14px;\n  background: transparent;\n  border: none;\n  border-bottom: 2px solid transparent;\n  color: var(--text-muted);\n  font-size: 11px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  letter-spacing: 0.3px;\n}\n\n.output-tab:hover {\n  color: var(--text-secondary);\n  background: rgba(255, 255, 255, 0.02);\n}\n\n.output-tab.active {\n  color: var(--c-mint-400);\n  border-bottom-color: var(--c-mint-400);\n  background: rgba(44, 201, 168, 0.05);\n}\n\n.console-badge {\n  background: var(--c-mint-500);\n  color: white;\n  font-size: 9px;\n  font-weight: 700;\n  padding: 2px 6px;\n  border-radius: 8px;\n  min-width: 16px;\n  text-align: center;\n}\n\n/* Console Output */\n.console-output {\n  flex: 1;\n  overflow: auto;\n  padding: 10px 14px;\n  font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;\n  font-size: 11px;\n  line-height: 1.6;\n  background: rgba(9, 12, 16, 0.95);\n}\n\n.console-entry {\n  padding: 5px 8px;\n  border-radius: 4px;\n  margin-bottom: 3px;\n  display: flex;\n  gap: 8px;\n  align-items: flex-start;\n  transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n  border-left: 2px solid transparent;\n}\n\n.console-entry:hover {\n  background: rgba(255, 255, 255, 0.02);\n}\n\n.console-time {\n  color: #484f58;\n  font-size: 9px;\n  flex-shrink: 0;\n  font-weight: 500;\n}\n\n.console-type {\n  font-weight: 600;\n  flex-shrink: 0;\n  font-size: 9px;\n  text-transform: uppercase;\n  letter-spacing: 0.4px;\n  padding: 1px 5px;\n  border-radius: 3px;\n}\n\n.console-message {\n  color: #c9d1d9;\n  word-break: break-all;\n}\n\n.console-log .console-type {\n  color: #6e7681;\n  background: rgba(110, 118, 129, 0.12);\n}\n\n.console-info .console-type {\n  color: #58a6ff;\n  background: rgba(88, 166, 255, 0.12);\n}\n\n.console-warn {\n  background: rgba(210, 153, 34, 0.06);\n  border-left-color: #d29922;\n}\n\n.console-warn .console-type {\n  color: #d29922;\n  background: rgba(210, 153, 34, 0.15);\n}\n\n.console-error {\n  background: rgba(248, 81, 73, 0.06);\n  border-left-color: #f85149;\n}\n\n.console-error .console-type {\n  color: #f85149;\n  background: rgba(248, 81, 73, 0.15);\n}\n\n.console-error .console-message {\n  color: #ffa198;\n}\n</style>\n"
  },
  {
    "path": "book/playground/src/main.ts",
    "content": "import { createApp } from \"vue\";\nimport App from \"./App.vue\";\nimport \"./style.css\";\nimport \"@mdi/font/css/materialdesignicons.css\";\n\ncreateApp(App).mount(\"#app\");\n"
  },
  {
    "path": "book/playground/src/style.css",
    "content": "/**\n * chibivue Playground Theme\n * Modern dark theme with Mint/Turquoise accents\n */\n\n:root {\n  /* Primary Brand Colors - Mint/Turquoise */\n  --c-mint-50: #e8faf6;\n  --c-mint-100: #c5f2e8;\n  --c-mint-200: #8be4d3;\n  --c-mint-300: #50d6bd;\n  --c-mint-400: #2cc9a8;\n  --c-mint-500: #1ab394;\n  --c-mint-600: #159d82;\n  --c-mint-700: #0f8770;\n  --c-mint-800: #0a715e;\n\n  /* Navy Colors */\n  --c-navy-900: #1a2744;\n  --c-navy-800: #243352;\n  --c-navy-700: #2e3f60;\n  --c-navy-600: #3a4d72;\n  --c-navy-500: #4a5f87;\n  --c-navy-400: #6b7fa3;\n  --c-navy-300: #9aaac4;\n  --c-navy-200: #c4d0e1;\n  --c-navy-100: #e4eaf2;\n\n  /* Duck Mascot Colors */\n  --c-duck-yellow: #f4d35e;\n\n  /* Dark Theme */\n  --bg-primary: #0a0f18;\n  --bg-secondary: #0c1320;\n  --bg-tertiary: #101828;\n  --bg-elevated: #182030;\n  --text-primary: #e4ebf1;\n  --text-secondary: #a8bcc8;\n  --text-muted: #6b7d8a;\n  --accent: var(--c-mint-400);\n  --accent-hover: var(--c-mint-300);\n  --accent-soft: rgba(44, 201, 168, 0.12);\n  --border: rgba(255, 255, 255, 0.06);\n  --border-light: rgba(255, 255, 255, 0.03);\n  --success: var(--c-mint-400);\n  --warning: var(--c-duck-yellow);\n}\n\n* {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n  background: var(--bg-primary);\n  color: var(--text-primary);\n  min-height: 100vh;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n#app {\n  height: 100vh;\n  display: flex;\n  flex-direction: column;\n}\n\n/* Scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(44, 201, 168, 0.3);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(44, 201, 168, 0.5);\n}\n\n/* Chapter Selector Dropdown (Teleported to body) */\n.selector-dropdown {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 460px;\n  max-height: 65vh;\n  background: linear-gradient(180deg, rgba(16, 24, 40, 0.98) 0%, rgba(12, 19, 32, 0.98) 100%);\n  border: 1px solid rgba(44, 201, 168, 0.1);\n  border-radius: 14px;\n  box-shadow:\n    0 0 0 1px rgba(255, 255, 255, 0.03),\n    0 25px 60px -15px rgba(0, 0, 0, 0.6),\n    0 0 40px rgba(44, 201, 168, 0.05);\n  display: flex;\n  flex-direction: column;\n  z-index: 9999;\n  animation: dropdown-appear 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n  backdrop-filter: blur(20px);\n}\n\n@keyframes dropdown-appear {\n  from {\n    opacity: 0;\n    transform: translate(-50%, -48%) scale(0.96);\n  }\n  to {\n    opacity: 1;\n    transform: translate(-50%, -50%) scale(1);\n  }\n}\n\n.selector-header {\n  padding: 14px;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.04);\n}\n\n.search-wrapper {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 10px 14px;\n  background: rgba(0, 0, 0, 0.25);\n  border: 1px solid rgba(255, 255, 255, 0.06);\n  border-radius: 10px;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.search-wrapper:focus-within {\n  border-color: rgba(44, 201, 168, 0.4);\n  box-shadow: 0 0 0 3px rgba(44, 201, 168, 0.1);\n  background: rgba(0, 0, 0, 0.3);\n}\n\n.search-icon {\n  font-size: 13px;\n  opacity: 0.5;\n}\n\n.search-input {\n  flex: 1;\n  background: transparent;\n  border: none;\n  color: var(--text-primary);\n  font-size: 13px;\n  outline: none;\n}\n\n.search-input::placeholder {\n  color: var(--text-muted);\n}\n\n.search-clear {\n  padding: 4px;\n  color: var(--text-muted);\n  cursor: pointer;\n  font-size: 11px;\n  transition: color 0.2s;\n  opacity: 0.7;\n}\n\n.search-clear:hover {\n  color: var(--text-primary);\n  opacity: 1;\n}\n\n.selector-content {\n  flex: 1;\n  overflow-y: auto;\n  padding: 6px;\n  max-height: 380px;\n}\n\n.selector-group {\n  margin-bottom: 6px;\n}\n\n.group-header {\n  padding: 8px 10px;\n  font-size: 10px;\n  font-weight: 600;\n  text-transform: uppercase;\n  letter-spacing: 0.6px;\n  color: var(--text-muted);\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.group-header::after {\n  content: '';\n  flex: 1;\n  height: 1px;\n  background: linear-gradient(90deg, rgba(255, 255, 255, 0.06) 0%, transparent 100%);\n}\n\n.selector-item {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 9px 12px;\n  margin: 1px 0;\n  border-radius: 8px;\n  font-size: 12px;\n  color: var(--text-secondary);\n  cursor: pointer;\n  transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.selector-item:hover {\n  background: rgba(255, 255, 255, 0.04);\n  color: var(--text-primary);\n}\n\n.selector-item.selected {\n  background: rgba(44, 201, 168, 0.1);\n  color: var(--c-mint-400);\n}\n\n.selector-item.highlighted {\n  background: rgba(44, 201, 168, 0.08);\n  outline: 1px solid rgba(44, 201, 168, 0.3);\n  color: var(--text-primary);\n}\n\n.item-check {\n  color: var(--c-mint-400);\n  font-weight: 600;\n  font-size: 13px;\n}\n\n.item-name {\n  flex: 1;\n}\n\n.selector-empty {\n  padding: 36px 20px;\n  text-align: center;\n  color: var(--text-muted);\n  font-size: 13px;\n}\n\n.selector-footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 10px 14px;\n  border-top: 1px solid rgba(255, 255, 255, 0.04);\n  background: rgba(0, 0, 0, 0.15);\n  border-radius: 0 0 14px 14px;\n}\n\n.footer-hint {\n  display: flex;\n  align-items: center;\n  gap: 5px;\n  color: var(--text-muted);\n  font-size: 10px;\n}\n\n.footer-hint kbd {\n  padding: 2px 5px;\n  background: rgba(255, 255, 255, 0.04);\n  border: 1px solid rgba(255, 255, 255, 0.06);\n  border-radius: 4px;\n  font-family: inherit;\n  font-size: 9px;\n  color: var(--text-secondary);\n}\n\n.chapter-count {\n  font-size: 10px;\n  color: var(--text-muted);\n}\n\n/* Selector backdrop */\n.selector-dropdown::before {\n  content: '';\n  position: fixed;\n  inset: 0;\n  background: rgba(0, 0, 0, 0.6);\n  backdrop-filter: blur(4px);\n  z-index: -1;\n}\n"
  },
  {
    "path": "book/playground/src/types.ts",
    "content": "export interface ChapterFile {\n  path: string;\n  content: string;\n}\n\nexport interface Chapter {\n  id: string;\n  section: string;\n  sectionOrder: string;\n  name: string;\n  files: ChapterFile[];\n  bookUrl: string; // URL to the online book chapter\n  vueDocUrl?: string; // Optional URL to related Vue.js documentation\n}\n"
  },
  {
    "path": "book/playground/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module \"*.vue\" {\n  import type { DefineComponent } from \"vue\";\n  const component: DefineComponent<{}, {}, any>;\n  export default component;\n}\n\ndeclare module \"@monaco-editor/loader\" {\n  import type * as Monaco from \"monaco-editor\";\n\n  interface Loader {\n    init(): Promise<typeof Monaco>;\n    config(options: {\n      paths?: { vs?: string };\n      \"vs/nls\"?: { availableLanguages?: Record<string, string> };\n    }): void;\n  }\n\n  const loader: Loader;\n  export default loader;\n}\n"
  },
  {
    "path": "book/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"preserve\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.vue\", \"scripts/**/*.ts\"]\n}\n"
  },
  {
    "path": "book/playground/vite.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport vue from \"@vitejs/plugin-vue\";\n\nexport default defineConfig({\n  plugins: [vue()],\n  server: {\n    headers: {\n      \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n      \"Cross-Origin-Opener-Policy\": \"same-origin\",\n    },\n  },\n});\n"
  },
  {
    "path": "examples/app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body style=\"margin: 0; height: 100vh\">\n    <div id=\"app\" style=\"height: 100%\"></div>\n    <script type=\"module\" src=\"src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/app/package.json",
    "content": "{\n  \"name\": \"@examples/app\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\"\n  },\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"devDependencies\": {\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "examples/app/src/App.vue",
    "content": "<script setup>\nimport { useRouter } from 'chibivue-router'\nconst router = useRouter()\n</script>\n\n<template>\n  <div id=\"pages-app\">\n    <header>\n      <nav>\n        <button @click=\"router.push('/')\">Top</button>\n        <button @click=\"router.push('/state')\">state</button>\n        <button @click=\"router.push('/directive')\">directive</button>\n        <button @click=\"router.push('/props-emits')\">props/emit</button>\n        <button @click=\"router.push('/compiler-macro')\">macro</button>\n        <button @click=\"router.push('/options-api')\">options api</button>\n        <button @click=\"router.push('/inline')\">inline</button>\n        <button @click=\"router.push('/store-counter')\">store</button>\n        <button @click=\"router.push('/todo-app')\">todo app</button>\n      </nav>\n    </header>\n\n    <div id=\"pages-content\">\n      <RouterView />\n    </div>\n  </div>\n</template>\n\n<style>\n#pages-app {\n  height: 100%;\n  font-family: 'Hannotate SC';\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  text-align: center;\n  color: #2c3e50;\n}\n\n#pages-content {\n  background: #efeeea;\n  padding: 20px;\n  height: 88%;\n}\n\n#pages-app header nav {\n  background: #1c273e;\n  height: 12%;\n  padding: 1rem 2rem;\n}\n\n#pages-app header nav button {\n  font-family: 'Hannotate SC';\n  text-decoration: underline;\n  border: none;\n  padding: 8px;\n  width: 120px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n\n#pages-app header nav button:hover {\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/components/CompilerMacroDemo.vue",
    "content": "<script setup>\nconst props = defineProps({\n  message: { type: String, default: 'Hello World' },\n})\n\nconst emit = defineEmits({ 'click:compiler-macro': null })\n\nconst invokeEmit = () => {\n  console.log('props.message: ', props)\n  emit('click:compiler-macro', 'Hello from CompilerMacroDemo')\n}\n</script>\n\n<template>\n  <div>\n    <p>{{ message }} (children)</p>\n    <button @click=\"invokeEmit\">emit</button>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/app/src/components/SimpleBtn.vue",
    "content": "<!-- button component (emit only) -->\n<script>\nexport default {\n  props: {\n    text: { type: String },\n  },\n  setup(_, { emit }) {\n    const handleClick = () => {\n      emit('click:simple-btn', 'Hello from SimpleBtn')\n    }\n    return { handleClick }\n  },\n}\n</script>\n\n<template>\n  <button @click=\"handleClick\">{{ text }}</button>\n</template>\n"
  },
  {
    "path": "examples/app/src/components/SimpleCard.vue",
    "content": "<script>\nexport default {\n  props: {\n    card: { type: Object },\n  },\n  setup(props) {},\n}\n</script>\n\n<template>\n  <div style=\"border: 1px solid #ccc; padding: 10px; margin: 10px\">\n    <h3>{{ card.title }}</h3>\n    <p>{{ card.content }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/app/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\nimport { router } from \"./router\";\n\nconst app = createApp(App);\napp.use(router);\napp.use(createStore());\napp.mount(\"#app\");\n"
  },
  {
    "path": "examples/app/src/router.ts",
    "content": "// @ts-nocheck\nimport { createRouter, createWebHistory } from \"chibivue-router\";\nimport PagesTop from \"./views/index.vue\";\nimport PagesState from \"./views/state.vue\";\nimport PagesDirective from \"./views/directive.vue\";\nimport PagesPropsEmits from \"./views/props-emits.vue\";\nimport PagesCompilerMacro from \"./views/compiler-macro.vue\";\nimport PagesOptionsApi from \"./views/options-api.vue\";\nimport PagesInline from \"./views/inline\";\n\nimport PagesStoreCounter from \"./views/store-counter.vue\";\nimport PagesTodoApp from \"./views/todo-list.vue\";\n\nexport const router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: \"/\", component: PagesTop },\n    { path: \"/state\", component: PagesState },\n    { path: \"/directive\", component: PagesDirective },\n    { path: \"/props-emits\", component: PagesPropsEmits },\n    { path: \"/compiler-macro\", component: PagesCompilerMacro },\n    { path: \"/options-api\", component: PagesOptionsApi },\n    { path: \"/inline\", component: PagesInline },\n\n    { path: \"/store-counter\", component: PagesStoreCounter },\n    { path: \"/todo-app\", component: PagesTodoApp },\n  ],\n});\n"
  },
  {
    "path": "examples/app/src/store/count.store.ts",
    "content": "import { ref } from \"chibivue\";\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", () => {\n  const count = ref(0);\n\n  const increment = () => {\n    count.value++;\n  };\n\n  const reset = () => {\n    count.value = 0;\n  };\n\n  return { count, increment, reset };\n});\n"
  },
  {
    "path": "examples/app/src/views/compiler-macro.vue",
    "content": "<script>\nimport { defineComponent, ref } from 'chibivue'\nimport CompilerMacroDemo from '../components/CompilerMacroDemo.vue'\n\nexport default defineComponent({\n  components: { CompilerMacroDemo },\n\n  setup() {\n    const message = ref('hello world')\n    const handleClick = (...args) => {\n      message.value = args.join(' ')\n    }\n    return { message, handleClick }\n  },\n})\n</script>\n\n<template>\n  <div id=\"compiler-macro\">\n    <h1>compiler macro</h1>\n    <CompilerMacroDemo :message=\"message\" @click:compiler-macro=\"handleClick\" />\n  </div>\n</template>\n\n<style>\n#compiler-macro button {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 200px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n#compiler-macro button:hover {\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/views/directive.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\n</script>\n\n<script setup>\n// for v-on\nconst handleClick = () => {\n  alert('clicked')\n}\n\n// for v-bind\nconst style = { color: '#876432', fontSize: '30px', 'font-weight': 'bold' }\n\n// for v-for\nconst list = [\n  { id: 1, text: 'foo' },\n  { id: 2, text: 'bar' },\n  { id: 3, text: 'baz' },\n]\n\n// for v-model\nconst input = ref('')\n</script>\n\n<template>\n  <div id=\"pages-directives\">\n    <h1>directives</h1>\n\n    <h3>v-on</h3>\n    <button @click=\"handleClick\">click me</button>\n    <hr />\n\n    <h3>v-bind</h3>\n    <div :style=\"style\">hello</div>\n    <hr />\n\n    <h3>v-for</h3>\n    <ul>\n      <li v-for=\"(item, i) in list\" :key=\"item.id\">\n        <p>text: {{ item.text }}, index: {{ i }}</p>\n      </li>\n    </ul>\n    <hr />\n\n    <h3>v-model</h3>\n    <label>\n      Input Data\n      <input v-model=\"input\" />\n    </label>\n    <p>value: \"{{ input }}\"</p>\n  </div>\n</template>\n\n<style>\n#pages-directives button {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 200px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n#pages-directives button:hover {\n  opacity: 0.8;\n}\n\n#pages-directives li {\n  list-style: none;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/views/index.vue",
    "content": "<template>\n  <div>\n    <h1>Top Page</h1>\n\n    <p>👋 Hello Chibi-vue!</p>\n\n    <p>\n      chibivue is minimal Vue.js v3 core implementations (reactivity, vnode,\n      component, compiler). \"chibi\" means \"small\" in Japanese.\n    </p>\n\n    <p>\n      This project began in February 2023 with the goal of simplifying the\n      understanding of Vue's core implementation.\n    </p>\n\n    <p>\n      Currently, I am still in the process of implementation, but after\n      implementation, I intend to post explanatory articles as well.\n    </p>\n\n    <p>(For now, I plan to post Japanese first.)</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/app/src/views/inline.ts",
    "content": "import type { Component } from \"chibivue\";\nimport { computed, defineComponent, h, ref } from \"chibivue\";\n\nconst InlineComponent: Component = defineComponent(() => {\n  const count = ref(0);\n  const increment = () => {\n    count.value++;\n  };\n\n  const countDouble = computed(() => count.value * 2);\n\n  return () =>\n    h(\"div\", [\n      h(\"h1\", `inline component`),\n      h(\"p\", `count: ${count.value}`),\n      h(\"p\", `countDouble: ${countDouble.value}`),\n      h(\"button\", { onClick: increment }, \"increment\"),\n    ]);\n});\n\nexport default InlineComponent;\n"
  },
  {
    "path": "examples/app/src/views/options-api.vue",
    "content": "<script>\nimport { defineComponent } from 'chibivue'\n\nexport default defineComponent({\n  data() {\n    return {\n      count: 0,\n    }\n  },\n  methods: {\n    increment() {\n      this.count++\n    },\n    reset() {\n      this.count = 0\n    },\n  },\n  computed: {\n    double() {\n      return this.count * 2\n    },\n  },\n})\n</script>\n\n<template>\n  <div id=\"pages-options-api\">\n    <h1>options api</h1>\n    <p>count: {{ count }}</p>\n    <p>double: {{ double }}</p>\n    <button @click=\"increment\">increment</button>\n    <button @click=\"reset\">reset</button>\n  </div>\n</template>\n\n<style>\n#pages-options-api button {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 200px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n#pages-options-api button:hover {\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/views/props-emits.vue",
    "content": "<script>\nimport { defineComponent, reactive } from 'chibivue'\nimport SimpleCard from '../components/SimpleCard.vue'\nimport SimpleBtn from '../components/SimpleBtn.vue'\n\nexport default defineComponent({\n  components: { SimpleCard, SimpleBtn },\n  setup() {\n    const card = reactive({\n      title: 'Hello World',\n      content: 'This is a simple card',\n    })\n\n    const changeTitle = () => {\n      card.title =\n        card.title === 'Hello World!!!!!!!!!!!'\n          ? 'Hello World??????????'\n          : 'Hello World!!!!!!!!!!!'\n    }\n\n    return { card, changeTitle }\n  },\n})\n</script>\n\n<template>\n  <div id=\"pages-props-emit\">\n    <h1>props/emits</h1>\n    <SimpleCard :card=\"card\" />\n    <SimpleBtn text=\"Change Title\" @click:simple-btn=\"changeTitle\" />\n  </div>\n</template>\n\n<style>\n#pages-props-emit button {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 200px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n#pages-props-emit button:hover {\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/views/state.vue",
    "content": "<script>\nimport { ref, reactive, computed } from 'chibivue'\n</script>\n\n<script setup>\nconst count = ref(0)\nconst increment = () => {\n  count.value++\n}\n\nconst count2 = reactive({ value: 0 })\nconst increment2 = () => {\n  count2.value++\n}\n\nconst double = computed(() => count.value * 2)\n</script>\n\n<template>\n  <div id=\"pages-state\">\n    <h1>state</h1>\n    <p>count: {{ count }} (ref)</p>\n    <p>double: {{ double }} (ref)</p>\n    <p>count: {{ count2.value }} (reactive)</p>\n    <button @click=\"increment\">increment (ref)</button>\n    <button @click=\"increment2\">increment (reactive)</button>\n  </div>\n</template>\n\n<style>\n#pages-state button {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 200px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n#pages-state button:hover {\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/views/store-counter.vue",
    "content": "<script setup>\nimport { useCounterStore } from '../store/count.store'\nconst { count: todoMaxLength, increment, reset } = useCounterStore()\n</script>\n\n<template>\n  <div id=\"pages-store\">\n    <h1>store</h1>\n    <p>todo max length: {{ todoMaxLength }} (global counter)</p>\n    <button @click=\"increment\">increment</button>\n    <button @click=\"reset\">reset</button>\n  </div>\n</template>\n\n<style>\n#pages-store button {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 200px;\n  background: #92b5a9;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n#pages-store button:hover {\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "examples/app/src/views/todo-list.vue",
    "content": "<script>\nimport { ref } from 'chibivue'\nimport { useCounterStore } from '../store/count.store'\nconst uuid = () => {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n    const r = (Math.random() * 16) | 0\n    const v = c === 'x' ? r : (r & 0x3) | 0x8\n    return v.toString(16)\n  })\n}\n</script>\n\n<script setup>\nconst newTodo = ref('')\nconst todos = ref(JSON.parse(localStorage.getItem('todos') ?? '[]'))\nconst { count: todoMaxLength } = useCounterStore()\n\nconst addTodo = () => {\n  if (todos.value.length >= todoMaxLength.value) {\n    alert('Todo list is full')\n    return\n  }\n  if (newTodo.value.trim()) {\n    todos.value = [\n      ...todos.value,\n      {\n        id: uuid(),\n        text: newTodo.value,\n        completed: false,\n      },\n    ]\n    localStorage.setItem('todos', JSON.stringify(todos.value))\n    newTodo.value = ''\n  } else {\n    alert('Please enter a todo')\n  }\n}\n\nconst toggleTodoCompletion = id => {\n  const todo = todos.value.find(t => t.id === id)\n  if (todo) {\n    todo.completed = !todo.completed\n    localStorage.setItem('todos', JSON.stringify(todos.value))\n  }\n}\n\nconst removeTodo = id => {\n  todos.value = todos.value.filter(t => t.id !== id)\n  localStorage.setItem('todos', JSON.stringify(todos.value))\n}\n</script>\n\n<template>\n  <div id=\"pages-todo\">\n    <h1>todo app</h1>\n    <div>\n      <p>todo max length: {{ todoMaxLength }} (global counter)</p>\n      <label for=\"new-todo-input\" style=\"display: none\"> new todo </label>\n      <input\n        id=\"new-todo-input\"\n        v-model=\"newTodo\"\n        type=\"text\"\n        placeholder=\"Enter a new todo\"\n        class=\"new-todo-input\"\n      />\n      <button class=\"create-todo-btn\" @click=\"addTodo\">Add Todo</button>\n    </div>\n    <ul>\n      <li v-for=\"(todo, i) in todos\" :key=\"todo.id\">\n        <label :for=\"`todo-${todo.id}-check`\" style=\"display: none\"\n          >todo check</label\n        >\n        <input\n          class=\"toggle-todo-completion\"\n          type=\"checkbox\"\n          :id=\"`todo-${todo.id}-check`\"\n          :value=\"todo.completed\"\n          @change=\"toggleTodoCompletion(todo.id)\"\n        />\n\n        <span :class=\"todo.completed ? 'completed' : ''\">{{ todo.text }}</span>\n        <button class=\"delete-todo-btn\" @click=\"removeTodo(todo.id)\">\n          Delete\n        </button>\n      </li>\n    </ul>\n  </div>\n</template>\n\n<style>\n#pages-todo .new-todo-input {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  width: 240px;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n\n.delete-todo-btn {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  background-color: #ff6347;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n\n.create-todo-btn {\n  font-family: 'Hannotate SC';\n  border: none;\n  padding: 8px;\n  background-color: #1e90ff;\n  color: #fff;\n  font-weight: 900;\n  font-size: 1rem;\n  border-radius: 4px;\n}\n\n#pages-todo button:hover {\n  opacity: 0.8;\n}\n\n#pages-todo ul {\n  width: 240px;\n  margin: auto;\n  margin-top: 24px;\n  padding: 0;\n}\n\n#pages-todo li {\n  align-items: center;\n  background-color: #fff;\n  border-radius: 8px;\n  display: flex;\n  justify-content: space-between;\n  list-style: none;\n  margin-bottom: 8px;\n  padding: 8px 16px;\n  width: 240px;\n}\n\n#pages-todo li .completed {\n  color: grey;\n  text-decoration: line-through;\n}\n</style>\n"
  },
  {
    "path": "examples/app/vite.config.ts",
    "content": "import { defineConfig, mergeConfig } from \"vite\";\nimport sharedConfig from \"../../vite.config.shared\";\n\nexport default mergeConfig(sharedConfig, defineConfig({}));\n"
  },
  {
    "path": "examples/vapor/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/vapor/package.json",
    "content": "{\n  \"name\": \"@examples/vapor\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\"\n  },\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"devDependencies\": {\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "examples/vapor/src/App.vue",
    "content": "<script>\nimport { defineComponent, ref, provide } from 'chibivue'\nimport MyVaporComponent from './MyComponent.vapor'\n\nexport default defineComponent({\n  components: { MyVaporComponent },\n  setup() {\n    const count = ref(0)\n    provide('App.vue count', count)\n    return { count }\n  },\n})\n</script>\n\n<template>\n  <div>\n    <button @click=\"count++\">App.vue {{ count }}</button>\n    <MyVaporComponent />\n  </div>\n</template>\n"
  },
  {
    "path": "examples/vapor/src/Counter.vue",
    "content": "<script setup>\nimport { ref, inject } from 'chibivue'\nconst count = ref(0)\nconst appVueCount = inject('App.vue count')\n</script>\n\n<template>\n  <div>\n    <button @click=\"count++\">\n      Counter.vue (in MyComponent.vapor) {{ count }}\n    </button>\n    <button @click=\"appVueCount++\">parent-count-incrementor</button>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/vapor/src/MyComponent.vapor.ts",
    "content": "import { type Ref, effect, h, inject, onBeforeMount, onMounted, ref } from \"chibivue\";\n\nimport {\n  type VaporComponent,\n  createComponent,\n  on,\n  setText,\n  template,\n} from \"@chibivue/runtime-vapor\";\n\n// @ts-expect-error\nimport Counter from \"./Counter.vue\";\n\nconst t0 = () => template('<div><button id=\"btn-on-vapor\">');\n\nconst t1 = () => template('<button id=\"parent-count-incrementor\">parent-count-incrementor');\n\nexport default ((self: any) => {\n  /*\n   *\n   * compiled from scripts\n   *\n   */\n  const count = ref(0);\n\n  const appVueCount = inject<Ref<number>>(\"App.vue count\")!;\n\n  onBeforeMount(() => {\n    console.log(\"before mount\", document.getElementById(\"btn-on-vapor\"));\n  });\n\n  onMounted(() => {\n    console.log(\"mounted\", document.getElementById(\"btn-on-vapor\"));\n  });\n\n  /*\n   *\n   * compiled from template\n   *\n   */\n  const div = t0();\n\n  const button0 = div.firstChild as Element;\n  const _button_text = \"MyComponent.vapor (in App.vue) {}\";\n  effect(() => {\n    setText(button0, _button_text, count.value);\n  });\n  on(button0, \"click\", () => count.value++);\n\n  const button1 = t1();\n  on(button1, \"click\", () => appVueCount.value++);\n  div.insertBefore(button1, button0.nextSibling);\n\n  createComponent(self, h(Counter, null, []), div);\n\n  return div;\n}) satisfies VaporComponent;\n"
  },
  {
    "path": "examples/vapor/src/main.ts",
    "content": "import { createApp } from \"chibivue\";\n\n// @ts-expect-error\nimport App from \"./App.vue\";\n\nconst app = createApp(App);\napp.mount(\"#app\");\n"
  },
  {
    "path": "examples/vapor/vite.config.ts",
    "content": "import { defineConfig, mergeConfig } from \"vite\";\nimport sharedConfig from \"../../vite.config.shared\";\n\nexport default mergeConfig(sharedConfig, defineConfig({}));\n"
  },
  {
    "path": "impl/@extensions/chibivue-fetch/package.json",
    "content": "{\n  \"name\": \"chibivue-fetch\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-fetch/src/index.ts",
    "content": "// Query Cache\nexport {\n  createQueryCache,\n  disposeQueryCache,\n  getActiveQueryCache,\n  setActiveQueryCache,\n  serializeQueryCache,\n  hydrateQueryCache,\n  toKeyHash,\n  isSubsetOf,\n} from \"./queryCache\";\nexport type { QueryCache } from \"./queryCache\";\n\n// Composables\nexport { useQuery } from \"./useQuery\";\nexport { useMutation } from \"./useMutation\";\n\n// Types\nexport type {\n  // Entry Key\n  JSONValue,\n  EntryKey,\n  EntryKeyFn,\n  // Data State\n  DataStateStatus,\n  AsyncStatus,\n  DataState,\n  DataState_Pending,\n  DataState_Error,\n  DataState_Success,\n  // Query\n  QueryMeta,\n  QueryContext,\n  UseQueryEntry,\n  UseQueryOptions,\n  UseQueryOptionsWithDefaults,\n  UseQueryReturn,\n  // Mutation\n  UseMutationOptions,\n  UseMutationReturn,\n  // Cache\n  QueryCacheOptions,\n  // SSR\n  UseQueryEntryNodeSerialized,\n  SerializedQueryCache,\n} from \"./types\";\n"
  },
  {
    "path": "impl/@extensions/chibivue-fetch/src/queryCache.ts",
    "content": "import type { App, InjectionKey, EffectScope } from \"chibivue\";\nimport { hasInjectionContext, inject, shallowRef, markRaw, triggerRef } from \"chibivue\";\nimport type {\n  EntryKey,\n  UseQueryEntry,\n  UseQueryOptionsWithDefaults,\n  DataState,\n  QueryCacheOptions,\n  SerializedQueryCache,\n  UseQueryEntryNodeSerialized,\n  QueryMeta,\n  QueryContext,\n} from \"./types\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_STALE_TIME = 5000; // 5 seconds\nconst DEFAULT_GC_TIME = 5 * 60 * 1000; // 5 minutes\n\n// ============================================================================\n// Key Utilities\n// ============================================================================\n\n/**\n * Convert entry key to cache key string\n */\nexport function toKeyHash(key: EntryKey): string {\n  return JSON.stringify(key, (_, value) => {\n    if (value && typeof value === \"object\" && !Array.isArray(value)) {\n      return Object.keys(value)\n        .sort()\n        .reduce(\n          (sorted, k) => {\n            sorted[k] = value[k];\n            return sorted;\n          },\n          {} as Record<string, unknown>,\n        );\n    }\n    return value;\n  });\n}\n\n/**\n * Check if a key is a subset of another key (for partial matching)\n */\nexport function isSubsetOf(subsetKey: EntryKey, fullKey: EntryKey): boolean {\n  if (subsetKey.length > fullKey.length) return false;\n  return subsetKey.every((item, index) => {\n    const fullItem = fullKey[index];\n    if (\n      typeof item === \"object\" &&\n      item !== null &&\n      typeof fullItem === \"object\" &&\n      fullItem !== null\n    ) {\n      return JSON.stringify(item) === JSON.stringify(fullItem);\n    }\n    return item === fullItem;\n  });\n}\n\n// ============================================================================\n// Query Cache Interface\n// ============================================================================\n\nexport interface QueryCache {\n  install: (app: App) => void;\n\n  /** Internal cache map */\n  caches: Map<string, UseQueryEntry>;\n\n  /** Default options */\n  options: Required<QueryCacheOptions>;\n\n  /** Create a new query entry */\n  create<TData = unknown, TError = Error>(\n    key: EntryKey,\n    options: UseQueryOptionsWithDefaults<TData, TError> | null,\n    data?: TData,\n    error?: TError | null,\n    when?: number,\n    meta?: QueryMeta,\n  ): UseQueryEntry<TData, TError>;\n\n  /** Ensure a query entry exists (create or retrieve) */\n  ensure<TData = unknown, TError = Error>(\n    key: EntryKey,\n    options: UseQueryOptionsWithDefaults<TData, TError>,\n  ): UseQueryEntry<TData, TError>;\n\n  /** Fetch data for a query entry */\n  fetch<TData = unknown, TError = Error>(\n    entry: UseQueryEntry<TData, TError>,\n  ): Promise<DataState<TData, TError>>;\n\n  /** Refresh a query (only if stale or has error) */\n  refresh<TData = unknown, TError = Error>(\n    entry: UseQueryEntry<TData, TError>,\n  ): Promise<DataState<TData, TError>>;\n\n  /** Invalidate a query (mark as stale) */\n  invalidate<TData = unknown, TError = Error>(entry: UseQueryEntry<TData, TError>): void;\n\n  /** Invalidate queries by key (partial match) */\n  invalidateQueries(key?: EntryKey): void;\n\n  /** Remove a query entry */\n  remove<TData = unknown, TError = Error>(entry: UseQueryEntry<TData, TError>): void;\n\n  /** Track a dependency for a query entry */\n  track<TData = unknown, TError = Error>(\n    entry: UseQueryEntry<TData, TError>,\n    effect: EffectScope | object | null,\n  ): void;\n\n  /** Untrack a dependency for a query entry */\n  untrack<TData = unknown, TError = Error>(\n    entry: UseQueryEntry<TData, TError>,\n    effect: EffectScope | object | null,\n  ): void;\n\n  /** Set query data manually */\n  setQueryData<TData>(key: EntryKey, data: TData): void;\n\n  /** Get query data */\n  getQueryData<TData>(key: EntryKey): TData | undefined;\n\n  /** Prefetch a query */\n  prefetchQuery<TData>(\n    key: EntryKey,\n    queryFn: (ctx: QueryContext) => Promise<TData>,\n    options?: Partial<UseQueryOptionsWithDefaults<TData>>,\n  ): Promise<void>;\n\n  /** Check if entry is stale */\n  isStale<TData = unknown, TError = Error>(entry: UseQueryEntry<TData, TError>): boolean;\n}\n\n// ============================================================================\n// Injection Key\n// ============================================================================\n\nexport const queryCacheSymbol: InjectionKey<QueryCache> = Symbol(\"chibivue-fetch:query-cache\");\n\nlet activeQueryCache: QueryCache | undefined;\n\nexport const setActiveQueryCache = (cache: QueryCache | undefined): QueryCache | undefined =>\n  (activeQueryCache = cache);\n\nexport const getActiveQueryCache = (): QueryCache | undefined => {\n  const cache = hasInjectionContext() && inject(queryCacheSymbol, null);\n\n  if (__DEV__ && !cache && typeof window === \"undefined\") {\n    console.warn(\n      `[chibivue-fetch]: QueryCache not found in context. This falls back to the global activeQueryCache ` +\n        `which exposes you to cross-request state pollution on the server.`,\n    );\n  }\n\n  return cache || activeQueryCache;\n};\n\n// ============================================================================\n// Create Query Cache\n// ============================================================================\n\nexport function createQueryCache(options: QueryCacheOptions = {}): QueryCache {\n  const caches = new Map<string, UseQueryEntry>();\n\n  const defaultOptions: Required<QueryCacheOptions> = {\n    staleTime: options.staleTime ?? DEFAULT_STALE_TIME,\n    gcTime: options.gcTime ?? DEFAULT_GC_TIME,\n  };\n\n  function scheduleGarbageCollection(entry: UseQueryEntry): void {\n    if (entry.deps.size > 0 || !entry.options) return;\n\n    clearTimeout(entry.gcTimeout);\n\n    // Abort any pending request\n    entry.pending?.abortController.abort();\n\n    if (Number.isFinite(entry.options.gcTime)) {\n      entry.gcTimeout = setTimeout(() => {\n        cache.remove(entry);\n      }, entry.options.gcTime);\n    }\n  }\n\n  const cache: QueryCache = {\n    install(app: App) {\n      setActiveQueryCache(cache);\n      app.provide(queryCacheSymbol, cache);\n    },\n\n    caches,\n    options: defaultOptions,\n\n    create<TData = unknown, TError = Error>(\n      key: EntryKey,\n      options: UseQueryOptionsWithDefaults<TData, TError> | null,\n      data?: TData,\n      error?: TError | null,\n      when?: number,\n      meta?: QueryMeta,\n    ): UseQueryEntry<TData, TError> {\n      const keyHash = toKeyHash(key);\n      const now = Date.now();\n\n      let initialState: DataState<TData, TError>;\n      if (data !== undefined) {\n        initialState = { status: \"success\", data, error: null };\n      } else if (error != null) {\n        initialState = { status: \"error\", data: undefined, error };\n      } else {\n        initialState = { status: \"pending\", data: undefined, error: null };\n      }\n\n      const entry = markRaw<UseQueryEntry<TData, TError>>({\n        state: shallowRef(initialState),\n        asyncStatus: shallowRef(\"idle\"),\n        key,\n        keyHash,\n        when: when != null ? now - when : 0, // Convert relative time back to absolute\n        meta,\n        deps: new Set(),\n        pending: null,\n        options,\n      });\n\n      return entry;\n    },\n\n    ensure<TData = unknown, TError = Error>(\n      key: EntryKey,\n      options: UseQueryOptionsWithDefaults<TData, TError>,\n    ): UseQueryEntry<TData, TError> {\n      const keyHash = toKeyHash(key);\n      let entry = caches.get(keyHash) as UseQueryEntry<TData, TError> | undefined;\n\n      if (!entry) {\n        // Get initial data if provided\n        let initialData: TData | undefined;\n        if (options.initialData !== undefined) {\n          initialData =\n            typeof options.initialData === \"function\"\n              ? (options.initialData as () => TData)()\n              : options.initialData;\n        }\n\n        entry = cache.create(key, options, initialData);\n        caches.set(keyHash, entry as UseQueryEntry);\n      } else {\n        // Update options if entry exists\n        entry.options = options;\n        clearTimeout(entry.gcTimeout);\n      }\n\n      return entry;\n    },\n\n    async fetch<TData = unknown, TError = Error>(\n      entry: UseQueryEntry<TData, TError>,\n    ): Promise<DataState<TData, TError>> {\n      if (!entry.options) {\n        throw new Error(\"[chibivue-fetch]: Cannot fetch without options\");\n      }\n\n      // Abort any existing request\n      entry.pending?.abortController.abort();\n\n      const abortController = new AbortController();\n      const when = Date.now();\n\n      const refreshCall = (async (): Promise<DataState<TData, TError>> => {\n        entry.asyncStatus.value = \"loading\";\n\n        let retryCount = 0;\n        const maxRetries =\n          entry.options!.retry === true\n            ? 3\n            : entry.options!.retry === false\n              ? 0\n              : entry.options!.retry;\n\n        while (true) {\n          try {\n            const data = await entry.options!.query({ signal: abortController.signal });\n\n            const newState: DataState<TData, TError> = {\n              status: \"success\",\n              data,\n              error: null,\n            };\n\n            entry.state.value = newState;\n            entry.when = Date.now();\n            entry.asyncStatus.value = \"idle\";\n            entry.pending = null;\n\n            return newState;\n          } catch (err) {\n            // Don't retry if aborted\n            if (abortController.signal.aborted) {\n              throw err;\n            }\n\n            retryCount++;\n            if (retryCount <= maxRetries) {\n              await new Promise((resolve) => setTimeout(resolve, entry.options!.retryDelay));\n              continue;\n            }\n\n            const newState: DataState<TData, TError> = {\n              status: \"error\",\n              data: entry.state.value.data,\n              error: err as TError,\n            };\n\n            entry.state.value = newState;\n            entry.when = Date.now();\n            entry.asyncStatus.value = \"idle\";\n            entry.pending = null;\n\n            return newState;\n          }\n        }\n      })();\n\n      entry.pending = { abortController, refreshCall, when };\n\n      return refreshCall;\n    },\n\n    async refresh<TData = unknown, TError = Error>(\n      entry: UseQueryEntry<TData, TError>,\n    ): Promise<DataState<TData, TError>> {\n      // Return existing pending request if one exists\n      if (entry.pending) {\n        return entry.pending.refreshCall;\n      }\n\n      // Only fetch if stale or has error\n      if (!cache.isStale(entry) && entry.state.value.status !== \"error\") {\n        return entry.state.value;\n      }\n\n      return cache.fetch(entry);\n    },\n\n    invalidate(entry: UseQueryEntry<any, any>): void {\n      entry.when = 0;\n      entry.pending?.abortController.abort();\n      entry.pending = null;\n    },\n\n    invalidateQueries(key?: EntryKey): void {\n      if (key) {\n        for (const entry of caches.values()) {\n          if (isSubsetOf(key, entry.key)) {\n            cache.invalidate(entry);\n          }\n        }\n      } else {\n        for (const entry of caches.values()) {\n          cache.invalidate(entry);\n        }\n      }\n    },\n\n    remove(entry: UseQueryEntry<any, any>): void {\n      clearTimeout(entry.gcTimeout);\n      entry.pending?.abortController.abort();\n      caches.delete(entry.keyHash);\n    },\n\n    track(entry: UseQueryEntry<any, any>, effect: EffectScope | object | null): void {\n      if (!effect) return;\n      entry.deps.add(effect);\n      clearTimeout(entry.gcTimeout);\n    },\n\n    untrack(entry: UseQueryEntry<any, any>, effect: EffectScope | object | null): void {\n      if (!effect) return;\n      entry.deps.delete(effect);\n      scheduleGarbageCollection(entry);\n    },\n\n    setQueryData<TData>(key: EntryKey, data: TData): void {\n      const keyHash = toKeyHash(key);\n      const entry = caches.get(keyHash);\n\n      if (entry) {\n        entry.state.value = { status: \"success\", data, error: null };\n        entry.when = Date.now();\n        triggerRef(entry.state);\n      } else {\n        // Create a new entry with the data\n        const newEntry = cache.create(key, null, data);\n        caches.set(keyHash, newEntry);\n      }\n    },\n\n    getQueryData<TData>(key: EntryKey): TData | undefined {\n      const keyHash = toKeyHash(key);\n      const entry = caches.get(keyHash);\n      if (!entry || entry.state.value.status !== \"success\") return undefined;\n      return entry.state.value.data as TData;\n    },\n\n    async prefetchQuery<TData>(\n      key: EntryKey,\n      queryFn: (ctx: QueryContext) => Promise<TData>,\n      options?: Partial<UseQueryOptionsWithDefaults<TData>>,\n    ): Promise<void> {\n      const fullOptions: UseQueryOptionsWithDefaults<TData> = {\n        key,\n        query: queryFn,\n        staleTime: options?.staleTime ?? defaultOptions.staleTime,\n        gcTime: options?.gcTime ?? defaultOptions.gcTime,\n        refetchOnMount: options?.refetchOnMount ?? true,\n        retry: options?.retry ?? 3,\n        retryDelay: options?.retryDelay ?? 1000,\n      };\n\n      const entry = cache.ensure(key, fullOptions);\n\n      // Only fetch if no data yet or stale\n      if (entry.state.value.status === \"pending\" || cache.isStale(entry)) {\n        await cache.fetch(entry);\n      }\n    },\n\n    isStale(entry: UseQueryEntry<any, any>): boolean {\n      if (!entry.options || !entry.when) return true;\n      return Date.now() >= entry.when + entry.options.staleTime;\n    },\n  };\n\n  return cache;\n}\n\n// ============================================================================\n// SSR Serialization / Hydration\n// ============================================================================\n\n/**\n * Serialize query cache for SSR\n */\nexport function serializeQueryCache(queryCache: QueryCache): SerializedQueryCache {\n  const serialized: SerializedQueryCache = {};\n  const now = Date.now();\n\n  for (const [keyHash, entry] of queryCache.caches) {\n    // Only serialize entries with data\n    if (entry.state.value.status === \"pending\" && entry.state.value.data === undefined) {\n      continue;\n    }\n\n    const state = entry.state.value;\n    const relativeWhen = entry.when ? now - entry.when : undefined;\n\n    serialized[keyHash] = [\n      state.data,\n      state.error,\n      relativeWhen,\n      entry.meta,\n    ] as UseQueryEntryNodeSerialized;\n  }\n\n  return serialized;\n}\n\n/**\n * Hydrate query cache from SSR state\n */\nexport function hydrateQueryCache(\n  queryCache: QueryCache,\n  serializedCache: SerializedQueryCache,\n): void {\n  for (const keyHash in serializedCache) {\n    const [data, error, when, meta] = serializedCache[keyHash] ?? [];\n\n    // Parse key from keyHash\n    const key = JSON.parse(keyHash) as EntryKey;\n\n    const entry = queryCache.create(key, null, data, error, when, meta);\n    queryCache.caches.set(keyHash, entry);\n  }\n}\n\n// ============================================================================\n// Dispose\n// ============================================================================\n\nexport function disposeQueryCache(cache: QueryCache): void {\n  for (const entry of cache.caches.values()) {\n    clearTimeout(entry.gcTimeout);\n    entry.pending?.abortController.abort();\n  }\n  cache.caches.clear();\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-fetch/src/types.ts",
    "content": "import type { Ref, ShallowRef, ComputedRef, EffectScope } from \"chibivue\";\n\n// ============================================================================\n// Entry Key Types (like Pinia Colada)\n// ============================================================================\n\nexport type JSONValue =\n  | string\n  | number\n  | boolean\n  | null\n  | JSONValue[]\n  | { [key: string]: JSONValue };\nexport type EntryKey = readonly JSONValue[];\nexport type EntryKeyFn = () => EntryKey;\n\n// ============================================================================\n// Data State Types (Pinia Colada pattern: pending | error | success)\n// ============================================================================\n\nexport type DataStateStatus = \"pending\" | \"error\" | \"success\";\nexport type AsyncStatus = \"idle\" | \"loading\";\n\nexport interface DataState_Pending {\n  status: \"pending\";\n  data: undefined;\n  error: null;\n}\n\nexport interface DataState_Error<TData = unknown, TError = Error> {\n  status: \"error\";\n  data: TData | undefined;\n  error: TError;\n}\n\nexport interface DataState_Success<TData = unknown> {\n  status: \"success\";\n  data: TData;\n  error: null;\n}\n\nexport type DataState<TData = unknown, TError = Error> =\n  | DataState_Pending\n  | DataState_Error<TData, TError>\n  | DataState_Success<TData>;\n\n// ============================================================================\n// Query Entry Types\n// ============================================================================\n\nexport interface QueryMeta {\n  [key: string]: unknown;\n}\n\nexport interface UseQueryEntry<TData = unknown, TError = Error> {\n  /** Current data state */\n  state: ShallowRef<DataState<TData, TError>>;\n  /** Async status: idle or loading */\n  asyncStatus: ShallowRef<AsyncStatus>;\n  /** Query key */\n  key: EntryKey;\n  /** Serialized key hash */\n  keyHash: string;\n  /** Last update timestamp */\n  when: number;\n  /** Custom metadata */\n  meta?: QueryMeta;\n  /** Active dependencies (components/scopes using this entry) */\n  deps: Set<EffectScope | object>;\n  /** Pending request info */\n  pending: null | {\n    abortController: AbortController;\n    refreshCall: Promise<DataState<TData, TError>>;\n    when: number;\n  };\n  /** Query options */\n  options: UseQueryOptionsWithDefaults<TData, TError> | null;\n  /** GC timeout ID */\n  gcTimeout?: ReturnType<typeof setTimeout>;\n}\n\n// ============================================================================\n// Query Options Types\n// ============================================================================\n\nexport interface UseQueryOptions<TData = unknown, TError = Error> {\n  /** Unique key for the query */\n  key: EntryKey | EntryKeyFn;\n  /** Function to fetch data */\n  query: (context: QueryContext) => Promise<TData>;\n  /** Time in ms before data is considered stale (default: 5000) */\n  staleTime?: number;\n  /** Time in ms to keep unused data in cache (default: 300000 / 5 minutes) */\n  gcTime?: number;\n  /** Whether to refetch on mount if data is stale (default: true) */\n  refetchOnMount?: boolean | \"always\";\n  /** Initial data to use before fetch completes */\n  initialData?: TData | (() => TData);\n  /** Whether the query is enabled (default: true) */\n  enabled?: boolean | Ref<boolean> | ComputedRef<boolean>;\n  /** Retry count on failure (default: 3) */\n  retry?: number | boolean;\n  /** Delay between retries in ms (default: 1000) */\n  retryDelay?: number;\n  /** Custom metadata */\n  meta?: QueryMeta;\n}\n\nexport interface UseQueryOptionsWithDefaults<TData = unknown, TError = Error> extends Required<\n  Pick<\n    UseQueryOptions<TData, TError>,\n    \"staleTime\" | \"gcTime\" | \"refetchOnMount\" | \"retry\" | \"retryDelay\"\n  >\n> {\n  key: EntryKey | EntryKeyFn;\n  query: (context: QueryContext) => Promise<TData>;\n  initialData?: TData | (() => TData);\n  enabled?: boolean | Ref<boolean> | ComputedRef<boolean>;\n  meta?: QueryMeta;\n}\n\nexport interface QueryContext {\n  /** Abort signal for cancellation */\n  signal: AbortSignal;\n}\n\n// ============================================================================\n// Query Result Types\n// ============================================================================\n\nexport interface UseQueryReturn<TData = unknown, TError = Error> {\n  /** Current data state */\n  state: ComputedRef<DataState<TData, TError>>;\n  /** Async status */\n  asyncStatus: ComputedRef<AsyncStatus>;\n  /** The fetched data */\n  data: ComputedRef<TData | undefined>;\n  /** Error if the query failed */\n  error: ComputedRef<TError | null>;\n  /** Current status */\n  status: ComputedRef<DataStateStatus>;\n  /** Whether initial data is being loaded (no data yet) */\n  isPending: ComputedRef<boolean>;\n  /** Whether currently loading (pending + fetching) */\n  isLoading: ComputedRef<boolean>;\n  /** Whether the query has successfully fetched */\n  isSuccess: ComputedRef<boolean>;\n  /** Whether the query failed */\n  isError: ComputedRef<boolean>;\n  /** Refresh the query (only if stale or error) */\n  refresh: () => Promise<DataState<TData, TError>>;\n  /** Refetch the query (always fetch) */\n  refetch: () => Promise<DataState<TData, TError>>;\n}\n\n// ============================================================================\n// Mutation Types\n// ============================================================================\n\nexport interface UseMutationOptions<\n  TData = unknown,\n  TError = Error,\n  TVariables = void,\n  TContext = unknown,\n> {\n  /** Function to perform the mutation */\n  mutation: (variables: TVariables) => Promise<TData>;\n  /** Called before mutation executes */\n  onMutate?: (variables: TVariables) => TContext | Promise<TContext>;\n  /** Callback on successful mutation */\n  onSuccess?: (\n    data: TData,\n    variables: TVariables,\n    context: TContext | undefined,\n  ) => void | Promise<void>;\n  /** Callback on failed mutation */\n  onError?: (\n    error: TError,\n    variables: TVariables,\n    context: TContext | undefined,\n  ) => void | Promise<void>;\n  /** Callback when mutation is settled (success or error) */\n  onSettled?: (\n    data: TData | undefined,\n    error: TError | null,\n    variables: TVariables,\n    context: TContext | undefined,\n  ) => void | Promise<void>;\n}\n\nexport interface UseMutationReturn<TData = unknown, TError = Error, TVariables = void> {\n  /** Current data state */\n  state: ComputedRef<DataState<TData, TError>>;\n  /** Async status */\n  asyncStatus: ComputedRef<AsyncStatus>;\n  /** The mutation result data */\n  data: ComputedRef<TData | undefined>;\n  /** Error if the mutation failed */\n  error: ComputedRef<TError | null>;\n  /** Current status */\n  status: ComputedRef<DataStateStatus>;\n  /** Whether initial state (no mutation yet) */\n  isPending: ComputedRef<boolean>;\n  /** Whether the mutation is in progress */\n  isLoading: ComputedRef<boolean>;\n  /** Whether the mutation was successful */\n  isSuccess: ComputedRef<boolean>;\n  /** Whether the mutation failed */\n  isError: ComputedRef<boolean>;\n  /** Current variables */\n  variables: ShallowRef<TVariables | undefined>;\n  /** Execute the mutation (fire and forget) */\n  mutate: (variables: TVariables) => void;\n  /** Execute the mutation and return a promise */\n  mutateAsync: (variables: TVariables) => Promise<TData>;\n  /** Reset the mutation state */\n  reset: () => void;\n}\n\n// ============================================================================\n// Query Cache Types\n// ============================================================================\n\nexport interface QueryCacheOptions {\n  /** Default stale time for all queries (default: 5000ms) */\n  staleTime?: number;\n  /** Default GC time for all queries (default: 300000ms / 5 minutes) */\n  gcTime?: number;\n}\n\n// ============================================================================\n// SSR Serialization Types\n// ============================================================================\n\n/**\n * Serialized format for query entry:\n * [data, error, when (relative), meta]\n */\nexport type UseQueryEntryNodeSerialized<TData = unknown, TError = Error> = [\n  data: TData | undefined,\n  error: TError | null,\n  when?: number,\n  meta?: QueryMeta,\n];\n\nexport type SerializedQueryCache = Record<string, UseQueryEntryNodeSerialized>;\n"
  },
  {
    "path": "impl/@extensions/chibivue-fetch/src/useMutation.ts",
    "content": "import { computed, shallowRef } from \"chibivue\";\nimport type { UseMutationOptions, UseMutationReturn, DataState, AsyncStatus } from \"./types\";\n\n// ============================================================================\n// useMutation\n// ============================================================================\n\nexport function useMutation<TData = unknown, TError = Error, TVariables = void, TContext = unknown>(\n  options: UseMutationOptions<TData, TError, TVariables, TContext>,\n): UseMutationReturn<TData, TError, TVariables> {\n  const { mutation, onMutate, onSuccess, onError, onSettled } = options;\n\n  // State\n  const state = shallowRef<DataState<TData, TError>>({\n    status: \"pending\",\n    data: undefined,\n    error: null,\n  });\n  const asyncStatus = shallowRef<AsyncStatus>(\"idle\");\n  const variables = shallowRef<TVariables | undefined>(undefined);\n\n  function reset(): void {\n    state.value = {\n      status: \"pending\",\n      data: undefined,\n      error: null,\n    };\n    asyncStatus.value = \"idle\";\n    variables.value = undefined;\n  }\n\n  async function mutateAsync(vars: TVariables): Promise<TData> {\n    variables.value = vars;\n    asyncStatus.value = \"loading\";\n\n    let context: TContext | undefined;\n\n    // Call onMutate before executing mutation\n    if (onMutate) {\n      try {\n        context = await onMutate(vars);\n      } catch {\n        // Ignore onMutate errors\n      }\n    }\n\n    try {\n      const result = await mutation(vars);\n\n      state.value = {\n        status: \"success\",\n        data: result,\n        error: null,\n      };\n      asyncStatus.value = \"idle\";\n\n      // Call onSuccess\n      if (onSuccess) {\n        await onSuccess(result, vars, context);\n      }\n\n      // Call onSettled\n      if (onSettled) {\n        await onSettled(result, null, vars, context);\n      }\n\n      return result;\n    } catch (err) {\n      const typedError = err as TError;\n\n      state.value = {\n        status: \"error\",\n        data: state.value.data, // Keep previous data\n        error: typedError,\n      };\n      asyncStatus.value = \"idle\";\n\n      // Call onError\n      if (onError) {\n        await onError(typedError, vars, context);\n      }\n\n      // Call onSettled\n      if (onSettled) {\n        await onSettled(undefined, typedError, vars, context);\n      }\n\n      throw err;\n    }\n  }\n\n  function mutate(vars: TVariables): void {\n    mutateAsync(vars).catch(() => {\n      // Error is already handled in mutateAsync\n    });\n  }\n\n  // Return computed refs\n  const stateComputed = computed(() => state.value);\n  const asyncStatusComputed = computed(() => asyncStatus.value);\n\n  return {\n    state: stateComputed,\n    asyncStatus: asyncStatusComputed,\n    data: computed(() => state.value.data),\n    error: computed(() => state.value.error),\n    status: computed(() => state.value.status),\n    isPending: computed(() => state.value.status === \"pending\"),\n    isLoading: computed(() => asyncStatus.value === \"loading\"),\n    isSuccess: computed(() => state.value.status === \"success\"),\n    isError: computed(() => state.value.status === \"error\"),\n    variables,\n    mutate,\n    mutateAsync,\n    reset,\n  };\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-fetch/src/useQuery.ts",
    "content": "import {\n  computed,\n  onMounted,\n  onUnmounted,\n  watch,\n  shallowRef,\n  getCurrentInstance,\n  isRef,\n} from \"chibivue\";\nimport { getActiveQueryCache, toKeyHash, type QueryCache } from \"./queryCache\";\nimport type {\n  EntryKey,\n  EntryKeyFn,\n  UseQueryOptions,\n  UseQueryOptionsWithDefaults,\n  UseQueryReturn,\n  UseQueryEntry,\n  DataState,\n} from \"./types\";\n\n// ============================================================================\n// Defaults\n// ============================================================================\n\nconst USE_QUERY_DEFAULTS = {\n  staleTime: 5000,\n  gcTime: 5 * 60 * 1000,\n  refetchOnMount: true,\n  retry: 3,\n  retryDelay: 1000,\n} as const;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\nfunction resolveKey(keyOrFn: EntryKey | EntryKeyFn): EntryKey {\n  return typeof keyOrFn === \"function\" ? keyOrFn() : keyOrFn;\n}\n\nfunction isEnabled(enabled: UseQueryOptions[\"enabled\"]): boolean {\n  if (enabled === undefined) return true;\n  if (isRef(enabled)) return enabled.value;\n  return enabled;\n}\n\n// ============================================================================\n// useQuery\n// ============================================================================\n\nexport function useQuery<TData = unknown, TError = Error>(\n  options: UseQueryOptions<TData, TError>,\n): UseQueryReturn<TData, TError> {\n  const cache = getActiveQueryCache();\n\n  if (__DEV__ && !cache) {\n    throw new Error(\n      `[chibivue-fetch]: QueryCache not found. Did you forget to call app.use(createQueryCache())?`,\n    );\n  }\n\n  const queryCache = cache!;\n\n  // Merge options with defaults\n  const fullOptions: UseQueryOptionsWithDefaults<TData, TError> = {\n    ...USE_QUERY_DEFAULTS,\n    ...options,\n    staleTime: options.staleTime ?? queryCache.options.staleTime,\n    gcTime: options.gcTime ?? queryCache.options.gcTime,\n  };\n\n  // Current entry reference (can change if key changes)\n  const entry = shallowRef<UseQueryEntry<TData, TError>>();\n\n  // Track current component instance for dependency tracking\n  const currentInstance = getCurrentInstance();\n\n  // Get or create entry\n  function ensureEntry(): UseQueryEntry<TData, TError> {\n    const key = resolveKey(fullOptions.key);\n    const currentEntry = entry.value;\n\n    // Check if key changed\n    if (currentEntry && toKeyHash(key) === currentEntry.keyHash) {\n      return currentEntry;\n    }\n\n    // Untrack old entry\n    if (currentEntry) {\n      queryCache.untrack(currentEntry, currentInstance);\n    }\n\n    // Get or create new entry\n    const newEntry = queryCache.ensure(key, fullOptions);\n    entry.value = newEntry;\n\n    return newEntry;\n  }\n\n  // Initialize entry\n  ensureEntry();\n\n  // Track this component as a dependency\n  function trackEntry(): void {\n    const e = entry.value;\n    if (e) {\n      queryCache.track(e, currentInstance);\n    }\n  }\n\n  // Untrack this component\n  function untrackEntry(): void {\n    const e = entry.value;\n    if (e) {\n      queryCache.untrack(e, currentInstance);\n    }\n  }\n\n  // Refresh: only fetch if stale or error\n  async function refresh(): Promise<DataState<TData, TError>> {\n    const e = ensureEntry();\n    if (!isEnabled(fullOptions.enabled)) {\n      return e.state.value;\n    }\n    return queryCache.refresh(e);\n  }\n\n  // Refetch: always fetch\n  async function refetch(): Promise<DataState<TData, TError>> {\n    const e = ensureEntry();\n    if (!isEnabled(fullOptions.enabled)) {\n      return e.state.value;\n    }\n    queryCache.invalidate(e);\n    return queryCache.fetch(e);\n  }\n\n  // Watch for key changes (reactive queries)\n  if (typeof fullOptions.key === \"function\") {\n    watch(\n      () => resolveKey(fullOptions.key),\n      (newKey, oldKey) => {\n        if (toKeyHash(newKey) !== toKeyHash(oldKey as EntryKey)) {\n          ensureEntry();\n          if (isEnabled(fullOptions.enabled)) {\n            refresh();\n          }\n        }\n      },\n      { deep: true },\n    );\n  }\n\n  // Watch enabled state\n  if (isRef(fullOptions.enabled) || typeof fullOptions.enabled === \"object\") {\n    watch(\n      () => isEnabled(fullOptions.enabled),\n      (enabled) => {\n        if (enabled) {\n          refresh();\n        }\n      },\n    );\n  }\n\n  // Lifecycle\n  onMounted(() => {\n    trackEntry();\n\n    const e = entry.value;\n    if (!e || !isEnabled(fullOptions.enabled)) return;\n\n    // Handle refetchOnMount\n    const shouldRefetch =\n      fullOptions.refetchOnMount === \"always\" ||\n      (fullOptions.refetchOnMount && queryCache.isStale(e));\n\n    if (shouldRefetch || e.state.value.status === \"pending\") {\n      refresh();\n    }\n  });\n\n  onUnmounted(() => {\n    untrackEntry();\n  });\n\n  // Initial fetch if enabled and no data\n  if (isEnabled(fullOptions.enabled) && entry.value?.state.value.status === \"pending\") {\n    refresh();\n  }\n\n  // Return computed refs\n  const state = computed(\n    () =>\n      entry.value?.state.value ?? ({ status: \"pending\", data: undefined, error: null } as const),\n  );\n  const asyncStatus = computed(() => entry.value?.asyncStatus.value ?? (\"idle\" as const));\n\n  return {\n    state,\n    asyncStatus,\n    data: computed(() => state.value.data),\n    error: computed(() => state.value.error),\n    status: computed(() => state.value.status),\n    isPending: computed(() => state.value.status === \"pending\"),\n    isLoading: computed(() => state.value.status === \"pending\" && asyncStatus.value === \"loading\"),\n    isSuccess: computed(() => state.value.status === \"success\"),\n    isError: computed(() => state.value.status === \"error\"),\n    refresh,\n    refetch,\n  };\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/package.json",
    "content": "{\n  \"name\": \"@chibivue/language-core\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Language core for chibivue SFC (Volar.js based)\",\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc -p tsconfig.json\",\n    \"dev\": \"tsc -w -p tsconfig.json\"\n  },\n  \"dependencies\": {\n    \"@volar/language-core\": \"~2.4.0\",\n    \"@volar/typescript\": \"~2.4.0\"\n  },\n  \"peerDependencies\": {\n    \"typescript\": \">=5.0.0\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/src/index.ts",
    "content": "// Language Plugin\nexport { createChibivueLanguagePlugin } from \"./languagePlugin\";\n\n// Virtual Code\nexport { ChibivueVirtualCode } from \"./virtualCode\";\n\n// SFC Parser\nexport { parseSfc } from \"./parseSfc\";\n\n// Types\nexport type {\n  SfcBlock,\n  SfcDescriptor,\n  ChibivueCodeInformation,\n  ChibivueCompilerOptions,\n  CodeSegment,\n} from \"./types\";\nexport { codeFeatures } from \"./types\";\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/src/languagePlugin.ts",
    "content": "import type { CodegenContext, LanguagePlugin, VirtualCode } from \"@volar/language-core\";\nimport type {} from \"@volar/typescript\"; // Module augmentation for typescript property\nimport { ChibivueVirtualCode } from \"./virtualCode\";\nimport type { ChibivueCompilerOptions } from \"./types\";\n\n// ============================================================================\n// Chibivue Language Plugin\n// ============================================================================\n\n/**\n * Create a Volar.js language plugin for chibivue SFC files\n */\nexport function createChibivueLanguagePlugin(\n  _options: ChibivueCompilerOptions = {},\n): LanguagePlugin<string, ChibivueVirtualCode> {\n  return {\n    /**\n     * Get language ID for a file\n     */\n    getLanguageId(scriptId: string): string | undefined {\n      if (scriptId.endsWith(\".vue\")) {\n        return \"vue\";\n      }\n      return undefined;\n    },\n\n    /**\n     * Create virtual code for a .vue file\n     */\n    createVirtualCode(\n      scriptId: string,\n      languageId: string,\n      snapshot: ts.IScriptSnapshot,\n      _ctx: CodegenContext<string>,\n    ): ChibivueVirtualCode | undefined {\n      if (languageId === \"vue\") {\n        return new ChibivueVirtualCode(scriptId, snapshot);\n      }\n      return undefined;\n    },\n\n    /**\n     * Update existing virtual code\n     */\n    updateVirtualCode(\n      _scriptId: string,\n      virtualCode: ChibivueVirtualCode,\n      snapshot: ts.IScriptSnapshot,\n      _ctx: CodegenContext<string>,\n    ): ChibivueVirtualCode {\n      virtualCode.update(snapshot);\n      return virtualCode;\n    },\n\n    /**\n     * TypeScript integration\n     */\n    typescript: {\n      extraFileExtensions: [\n        {\n          extension: \"vue\",\n          isMixedContent: true,\n          scriptKind: 7, // ts.ScriptKind.Deferred\n        },\n      ],\n\n      getServiceScript(rootVirtualCode: ChibivueVirtualCode) {\n        // Find the TypeScript embedded code\n        for (const code of rootVirtualCode.embeddedCodes ?? []) {\n          if (code.id === \"ts\") {\n            return {\n              code,\n              extension: \".ts\" as const,\n              scriptKind: 3, // ts.ScriptKind.TS\n            };\n          }\n        }\n        return undefined;\n      },\n    },\n  };\n}\n\n// TypeScript types\ndeclare namespace ts {\n  interface IScriptSnapshot {\n    getText(start: number, end: number): string;\n    getLength(): number;\n    getChangeRange(\n      oldSnapshot: IScriptSnapshot,\n    ): { span: { start: number; length: number }; newLength: number } | undefined;\n  }\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/src/parseSfc.ts",
    "content": "import type { SfcBlock, SfcDescriptor } from \"./types\";\n\n// ============================================================================\n// Simple SFC Parser\n// ============================================================================\n\n/**\n * Parse a .vue SFC file into its constituent blocks\n * This is a minimal implementation for language tools\n */\nexport function parseSfc(content: string, fileName: string): SfcDescriptor {\n  const descriptor: SfcDescriptor = {\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n    customBlocks: [],\n  };\n\n  // Match all top-level blocks\n  const blockRegex = /<(\\w+)([^>]*)>([\\s\\S]*?)<\\/\\1>/g;\n  let match: RegExpExecArray | null;\n\n  while ((match = blockRegex.exec(content)) !== null) {\n    const [fullMatch, tagName, attrsString, blockContent] = match;\n    const startOffset = match.index;\n    const endOffset = startOffset + fullMatch.length;\n\n    // Parse attributes\n    const attrs = parseAttributes(attrsString);\n\n    // Calculate line/column positions\n    const startPos = offsetToPosition(content, startOffset);\n    const endPos = offsetToPosition(content, endOffset);\n\n    // Calculate content offset (after opening tag)\n    const openingTagLength = `<${tagName}${attrsString}>`.length;\n    const contentStartOffset = startOffset + openingTagLength;\n    const contentStartPos = offsetToPosition(content, contentStartOffset);\n    const contentEndOffset = endOffset - `</${tagName}>`.length;\n    const contentEndPos = offsetToPosition(content, contentEndOffset);\n\n    const block: SfcBlock = {\n      type: tagName,\n      content: blockContent,\n      loc: {\n        start: contentStartPos,\n        end: contentEndPos,\n      },\n      attrs,\n      lang: typeof attrs.lang === \"string\" ? attrs.lang : undefined,\n    };\n\n    // Categorize block\n    switch (tagName) {\n      case \"template\":\n        descriptor.template = block;\n        break;\n      case \"script\":\n        if (\"setup\" in attrs) {\n          descriptor.scriptSetup = block;\n        } else {\n          descriptor.script = block;\n        }\n        break;\n      case \"style\":\n        descriptor.styles.push(block);\n        break;\n      default:\n        descriptor.customBlocks.push(block);\n        break;\n    }\n  }\n\n  return descriptor;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Parse HTML-like attributes from a string\n */\nfunction parseAttributes(attrsString: string): Record<string, string | true> {\n  const attrs: Record<string, string | true> = {};\n  const attrRegex = /(\\w+)(?:=[\"']([^\"']*)[\"'])?/g;\n  let match: RegExpExecArray | null;\n\n  while ((match = attrRegex.exec(attrsString)) !== null) {\n    const [, name, value] = match;\n    attrs[name] = value ?? true;\n  }\n\n  return attrs;\n}\n\n/**\n * Convert byte offset to line/column position\n */\nfunction offsetToPosition(\n  content: string,\n  offset: number,\n): { line: number; column: number; offset: number } {\n  let line = 1;\n  let column = 1;\n\n  for (let i = 0; i < offset && i < content.length; i++) {\n    if (content[i] === \"\\n\") {\n      line++;\n      column = 1;\n    } else {\n      column++;\n    }\n  }\n\n  return { line, column, offset };\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/src/types.ts",
    "content": "import type { CodeInformation } from \"@volar/language-core\";\n\n// ============================================================================\n// SFC Block Types\n// ============================================================================\n\nexport interface SfcBlock {\n  type: string;\n  content: string;\n  loc: {\n    start: { line: number; column: number; offset: number };\n    end: { line: number; column: number; offset: number };\n  };\n  attrs: Record<string, string | true>;\n  lang?: string;\n}\n\nexport interface SfcDescriptor {\n  template: SfcBlock | null;\n  script: SfcBlock | null;\n  scriptSetup: SfcBlock | null;\n  styles: SfcBlock[];\n  customBlocks: SfcBlock[];\n}\n\n// ============================================================================\n// Code Information Types (for Volar.js)\n// ============================================================================\n\nexport interface ChibivueCodeInformation extends CodeInformation {\n  /** Enable diagnostic verification */\n  verification?: boolean;\n  /** Enable auto-completion */\n  completion?: boolean;\n  /** Enable semantic tokens (syntax highlighting) */\n  semantic?: boolean;\n  /** Enable navigation (go to definition, rename) */\n  navigation?: boolean | { shouldRename?: () => boolean };\n}\n\n// ============================================================================\n// Compiler Options\n// ============================================================================\n\nexport interface ChibivueCompilerOptions {\n  /** Target chibivue version */\n  target?: 3;\n  /** Check unknown props */\n  checkUnknownProps?: boolean;\n  /** Check unknown events */\n  checkUnknownEvents?: boolean;\n}\n\n// ============================================================================\n// Code Segment Types\n// ============================================================================\n\n/**\n * Code segment with source mapping information\n * [content, sourceId, sourceOffset, codeInfo]\n */\nexport type CodeSegment =\n  | string\n  | [content: string, sourceId: string, sourceOffset: number, codeInfo: ChibivueCodeInformation];\n\n// ============================================================================\n// Code Features (what IDE features to enable for code ranges)\n// ============================================================================\n\nexport const codeFeatures = {\n  all: {\n    verification: true,\n    completion: true,\n    semantic: true,\n    navigation: true,\n  } satisfies ChibivueCodeInformation,\n\n  verification: {\n    verification: true,\n  } satisfies ChibivueCodeInformation,\n\n  completion: {\n    completion: true,\n  } satisfies ChibivueCodeInformation,\n\n  navigation: {\n    navigation: true,\n  } satisfies ChibivueCodeInformation,\n\n  navigationWithoutRename: {\n    navigation: {\n      shouldRename: () => false,\n    },\n  } satisfies ChibivueCodeInformation,\n};\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/src/virtualCode.ts",
    "content": "import type { VirtualCode, CodeMapping, Mapping } from \"@volar/language-core\";\nimport { parseSfc } from \"./parseSfc\";\nimport type { SfcDescriptor, ChibivueCodeInformation, CodeSegment } from \"./types\";\nimport { codeFeatures } from \"./types\";\n\n// ============================================================================\n// Chibivue Virtual Code\n// ============================================================================\n\n/**\n * Virtual code representation for a .vue file\n * Implements Volar.js VirtualCode interface\n */\nexport class ChibivueVirtualCode implements VirtualCode {\n  id = \"root\";\n  languageId = \"typescript\";\n  snapshot: ts.IScriptSnapshot;\n  mappings: CodeMapping[] = [];\n  embeddedCodes: VirtualCode[] = [];\n\n  private sfc: SfcDescriptor;\n  private fileName: string;\n\n  constructor(fileName: string, snapshot: ts.IScriptSnapshot) {\n    this.fileName = fileName;\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, fileName);\n    this.generateVirtualCode();\n  }\n\n  /**\n   * Update the virtual code when the source changes\n   */\n  update(snapshot: ts.IScriptSnapshot): void {\n    this.snapshot = snapshot;\n    const content = snapshot.getText(0, snapshot.getLength());\n    this.sfc = parseSfc(content, this.fileName);\n    this.generateVirtualCode();\n  }\n\n  /**\n   * Generate virtual TypeScript code from SFC\n   */\n  private generateVirtualCode(): void {\n    const segments: CodeSegment[] = [];\n    const sourceContent = this.snapshot.getText(0, this.snapshot.getLength());\n\n    // Generate script/scriptSetup code\n    this.generateScriptCode(segments);\n\n    // Generate template type-checking code\n    this.generateTemplateCode(segments);\n\n    // Build final code and mappings\n    const { code, mappings } = this.buildCode(segments, sourceContent);\n\n    // Create embedded code for the generated TypeScript\n    this.embeddedCodes = [\n      {\n        id: \"ts\",\n        languageId: \"typescript\",\n        snapshot: {\n          getText: (start, end) => code.slice(start, end),\n          getLength: () => code.length,\n          getChangeRange: () => undefined,\n        },\n        mappings,\n        embeddedCodes: [],\n      },\n    ];\n\n    // Root mappings (map entire file)\n    this.mappings = [\n      {\n        sourceOffsets: [0],\n        generatedOffsets: [0],\n        lengths: [sourceContent.length],\n        data: codeFeatures.all,\n      },\n    ];\n  }\n\n  /**\n   * Generate TypeScript code from script/scriptSetup blocks\n   */\n  private generateScriptCode(segments: CodeSegment[]): void {\n    const { script, scriptSetup } = this.sfc;\n\n    // Import chibivue types\n    segments.push(`import { defineComponent, ref, computed, reactive } from \"chibivue\";\\n\\n`);\n\n    // Script setup block\n    if (scriptSetup) {\n      const lang = scriptSetup.lang || \"ts\";\n      segments.push(`// <script setup>\\n`);\n\n      // Map the script setup content\n      segments.push([\n        scriptSetup.content,\n        \"scriptSetup\",\n        scriptSetup.loc.start.offset,\n        codeFeatures.all,\n      ]);\n\n      segments.push(`\\n// </script setup>\\n\\n`);\n    }\n\n    // Regular script block\n    if (script) {\n      segments.push(`// <script>\\n`);\n\n      segments.push([script.content, \"script\", script.loc.start.offset, codeFeatures.all]);\n\n      segments.push(`\\n// </script>\\n\\n`);\n    }\n\n    // Default export if no script\n    if (!script && !scriptSetup) {\n      segments.push(`export default defineComponent({});\\n\\n`);\n    }\n  }\n\n  /**\n   * Generate TypeScript code for template type-checking\n   */\n  private generateTemplateCode(segments: CodeSegment[]): void {\n    const { template } = this.sfc;\n\n    if (!template) return;\n\n    segments.push(`// Template type-checking\\n`);\n    segments.push(`declare const __VLS_template: () => void;\\n`);\n\n    // Extract and type-check interpolations {{ ... }}\n    const interpolationRegex = /\\{\\{\\s*([\\s\\S]*?)\\s*\\}\\}/g;\n    let match: RegExpExecArray | null;\n    let index = 0;\n\n    while ((match = interpolationRegex.exec(template.content)) !== null) {\n      const expression = match[1];\n      const expressionOffset =\n        template.loc.start.offset + match.index + match[0].indexOf(expression);\n\n      segments.push(`// Interpolation ${index}\\n`);\n      segments.push(`(() => {\\n  const __expr${index} = (`);\n\n      // Map the expression back to template\n      segments.push([expression, \"template\", expressionOffset, codeFeatures.all]);\n\n      segments.push(`);\\n})();\\n`);\n      index++;\n    }\n\n    segments.push(`\\n`);\n  }\n\n  /**\n   * Build final code string and mappings from segments\n   */\n  private buildCode(\n    segments: CodeSegment[],\n    sourceContent: string,\n  ): { code: string; mappings: CodeMapping[] } {\n    let code = \"\";\n    const mappings: CodeMapping[] = [];\n\n    for (const segment of segments) {\n      if (typeof segment === \"string\") {\n        code += segment;\n      } else {\n        const [content, sourceId, sourceOffset, codeInfo] = segment;\n        const generatedOffset = code.length;\n\n        // Add mapping\n        mappings.push({\n          sourceOffsets: [sourceOffset],\n          generatedOffsets: [generatedOffset],\n          lengths: [content.length],\n          data: codeInfo,\n        });\n\n        code += content;\n      }\n    }\n\n    return { code, mappings };\n  }\n}\n\n// TypeScript types\ndeclare namespace ts {\n  interface IScriptSnapshot {\n    getText(start: number, end: number): string;\n    getLength(): number;\n    getChangeRange(\n      oldSnapshot: IScriptSnapshot,\n    ): { span: { start: number; length: number }; newLength: number } | undefined;\n  }\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-core/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"ES2020\"],\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"moduleResolution\": \"node\"\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-server/bin/chibivue-language-server.js",
    "content": "#!/usr/bin/env node\n\nrequire(\"../dist/server.js\");\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-server/package.json",
    "content": "{\n  \"name\": \"@chibivue/language-server\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Language server for chibivue SFC\",\n  \"main\": \"./dist/index.js\",\n  \"bin\": {\n    \"chibivue-language-server\": \"./bin/chibivue-language-server.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"bin\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc -p tsconfig.json\",\n    \"dev\": \"tsc -w -p tsconfig.json\"\n  },\n  \"dependencies\": {\n    \"@chibivue/language-core\": \"workspace:*\",\n    \"@volar/language-core\": \"~2.4.0\",\n    \"@volar/language-server\": \"~2.4.0\",\n    \"@volar/language-service\": \"~2.4.0\",\n    \"@volar/typescript\": \"~2.4.0\",\n    \"volar-service-typescript\": \"0.0.62\",\n    \"vscode-uri\": \"^3.0.0\"\n  },\n  \"peerDependencies\": {\n    \"typescript\": \">=5.0.0\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-server/src/index.ts",
    "content": "// Re-export from server\nexport * from \"./server\";\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-server/src/server.ts",
    "content": "import {\n  createConnection,\n  createServer,\n  createSimpleProject,\n  loadTsdkByPath,\n} from \"@volar/language-server/node\";\nimport type { LanguagePlugin } from \"@volar/language-core\";\nimport type {} from \"@volar/typescript\"; // Module augmentation for typescript property\nimport { create as createTypeScriptServices } from \"volar-service-typescript\";\nimport { URI } from \"vscode-uri\";\n\n// ============================================================================\n// Chibivue Language Server\n// ============================================================================\n\nconst connection = createConnection();\n\nconst server = createServer(connection);\n\nconnection.listen();\n\nconnection.onInitialize((params) => {\n  // Load TypeScript SDK\n  const tsdk = params.initializationOptions?.typescript?.tsdk;\n  const ts = tsdk ? loadTsdkByPath(tsdk, params.locale).typescript : require(\"typescript\");\n\n  // Create language plugin for URI-based file identification\n  const chibivuePlugin: LanguagePlugin<URI> = {\n    getLanguageId(uri: URI): string | undefined {\n      if (uri.path.endsWith(\".vue\")) {\n        return \"vue\";\n      }\n      return undefined;\n    },\n\n    typescript: {\n      extraFileExtensions: [\n        {\n          extension: \"vue\",\n          isMixedContent: true,\n          scriptKind: 7, // ts.ScriptKind.Deferred\n        },\n      ],\n\n      getServiceScript(_rootVirtualCode) {\n        return undefined;\n      },\n    },\n  };\n\n  // Create project\n  const project = createSimpleProject([chibivuePlugin]);\n\n  // Create TypeScript language service plugins\n  const languageServicePlugins = createTypeScriptServices(ts);\n\n  return server.initialize(params, project, languageServicePlugins);\n});\n\nconnection.onInitialized(() => {\n  server.initialized();\n});\n\nconnection.onShutdown(() => {\n  server.shutdown();\n});\n"
  },
  {
    "path": "impl/@extensions/chibivue-language-server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"ES2020\"],\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"moduleResolution\": \"node\"\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/package.json",
    "content": "{\n  \"name\": \"chibivue-router\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/RouterView.ts",
    "content": "import { type ComponentOptions, Fragment, h, inject } from \"chibivue\";\nimport { routerViewLocationKey } from \"./injectionSymbols\";\n\nexport const RouterViewImpl: ComponentOptions = {\n  name: \"RouterView\",\n  setup() {\n    const injectedRoute = inject(routerViewLocationKey)!;\n\n    return () => {\n      const ViewComponent = injectedRoute.value.component;\n\n      // NOTE: wrap in Fragment to render by patch children:\n      // prettier-ignore\n      const component = h(Fragment, [h(ViewComponent, { key: injectedRoute.value.fullPath }),]);\n\n      return component;\n    };\n  },\n};\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/history.ts",
    "content": "export interface RouterHistory {\n  location: Location;\n  push(to: string): void;\n  replace(to: string): void;\n  go(delta: number, triggerListeners?: boolean): void;\n}\n\nexport const createWebHistory = (): RouterHistory => {\n  return {\n    location: window.location,\n    push(to: string) {\n      window.history.pushState({}, \"\", to);\n    },\n    replace(to: string) {\n      window.history.replaceState({}, \"\", to);\n    },\n    go(delta: number, triggerListeners?: boolean) {\n      window.history.go(delta);\n    },\n  };\n};\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/index.ts",
    "content": "export { createRouter } from \"./router\";\nexport { useRouter, useRoute } from \"./useApi\";\nexport { createWebHistory } from \"./history\";\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/injectionSymbols.ts",
    "content": "import type { ComputedRef, InjectionKey, Ref } from \"@chibivue/runtime-core\";\nimport type { Router } from \"./router\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\n\nexport const routerKey = Symbol() as InjectionKey<Router>;\n\nexport const routeLocationKey = Symbol() as InjectionKey<\n  ComputedRef<RouteLocationNormalizedLoaded>\n>;\n\nexport const routerViewLocationKey = Symbol() as InjectionKey<Ref<RouteLocationNormalizedLoaded>>;\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/router.ts",
    "content": "import { type App, computed, reactive, ref } from \"chibivue\";\nimport { routeLocationKey, routerKey, routerViewLocationKey } from \"./injectionSymbols\";\nimport type { RouterHistory } from \"./history\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\nimport { RouterViewImpl } from \"./RouterView\";\n\nexport interface RouteRecord {\n  path: string;\n  component: any;\n}\n\nexport interface RouterOptions {\n  routes: Readonly<RouteRecord[]>;\n  history: RouterHistory;\n}\n\nexport interface Router {\n  install(app: App): void;\n\n  push(to: string): void;\n  replace(to: string): void;\n}\n\nexport function createRouter(options: RouterOptions): Router {\n  const routerHistory = options.history;\n  const resolve = (to: string) => {\n    const route = options.routes.find((route) => route.path === to);\n    return {\n      fullPath: to,\n      component: route?.component ?? null,\n    };\n  };\n\n  const currentRoute = ref<RouteLocationNormalizedLoaded>({\n    fullPath: routerHistory.location.pathname,\n    component: resolve(routerHistory.location.pathname).component,\n  });\n\n  function push(to: string) {\n    routerHistory.push(to);\n    currentRoute.value = resolve(to);\n  }\n\n  function replace(to: string) {\n    routerHistory.replace(to);\n    currentRoute.value = resolve(to);\n  }\n\n  const router: Router = {\n    push,\n    replace,\n    install(app: App) {\n      const router = this;\n      app.component(\"RouterView\", RouterViewImpl);\n\n      const reactiveRoute = computed(() => currentRoute.value);\n      app.provide(routerKey, router);\n      app.provide(routeLocationKey, reactive(reactiveRoute));\n      app.provide(routerViewLocationKey, currentRoute);\n    },\n  };\n\n  return router;\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/types/index.ts",
    "content": "export interface RouteLocationNormalizedLoaded {\n  fullPath: string;\n  component: any;\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-router/src/useApi.ts",
    "content": "import { type ComputedRef, inject } from \"chibivue\";\nimport { routeLocationKey, routerKey } from \"./injectionSymbols\";\nimport type { Router } from \"./router\";\nimport type { RouteLocationNormalizedLoaded } from \"./types\";\n\nexport function useRouter(): Router {\n  return inject(routerKey)!;\n}\n\nexport function useRoute(): ComputedRef<RouteLocationNormalizedLoaded> {\n  return inject(routeLocationKey)!;\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-store/package.json",
    "content": "{\n  \"name\": \"chibivue-store\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-store/src/createStore.ts",
    "content": "import { type App, effectScope, markRaw, ref, type Ref } from \"chibivue\";\nimport {\n  type StateTree,\n  type Store,\n  type StoreGeneric,\n  type StorePlugin,\n  setActiveStore,\n  storeSymbol,\n} from \"./rootStore\";\n\nexport function createStore(): Store {\n  const scope = effectScope();\n\n  const state = scope.run<Ref<Record<string, StateTree>>>(() =>\n    ref<Record<string, StateTree>>({}),\n  )!;\n\n  let _p: StorePlugin[] = [];\n  let toBeInstalled: StorePlugin[] = [];\n\n  const store: Store = markRaw({\n    install(app: App) {\n      setActiveStore(store);\n      store._a = app;\n      app.provide(storeSymbol, store);\n      app.config.globalProperties.$store = store;\n      toBeInstalled.forEach((plugin) => _p.push(plugin));\n      toBeInstalled = [];\n    },\n\n    use(plugin: StorePlugin) {\n      if (!this._a) {\n        toBeInstalled.push(plugin);\n      } else {\n        _p.push(plugin);\n      }\n      return this;\n    },\n\n    _p,\n    _a: null,\n    _e: scope,\n    _s: new Map<string, StoreGeneric>(),\n    state,\n  });\n\n  return store;\n}\n\n/**\n * Dispose a Store instance by stopping its effectScope and removing the state, plugins and stores.\n */\nexport function disposeStore(store: Store): void {\n  store._e.stop();\n  store._s.clear();\n  store._p.splice(0);\n  store.state.value = {};\n  store._a = null;\n}\n"
  },
  {
    "path": "impl/@extensions/chibivue-store/src/index.ts",
    "content": "export { createStore, disposeStore } from \"./createStore\";\nexport { defineStore } from \"./store\";\nexport type { StoreDefinition, StoreInstance } from \"./store\";\nexport type { StateTree, Store, StorePlugin, StorePluginContext } from \"./rootStore\";\nexport { setActiveStore, getActiveStore } from \"./rootStore\";\n"
  },
  {
    "path": "impl/@extensions/chibivue-store/src/rootStore.ts",
    "content": "import type { App, EffectScope, InjectionKey, Ref } from \"chibivue\";\nimport { hasInjectionContext, inject } from \"chibivue\";\n\nexport type StateTree = Record<string | number | symbol, any>;\n\nexport interface Store {\n  install: (app: App) => void;\n  /** Add a store plugin */\n  use(plugin: StorePlugin): Store;\n  /** Root state - stores all store states as ref */\n  state: Ref<Record<string, StateTree>>;\n  /** Installed store plugins */\n  _p: StorePlugin[];\n  /** App linked to this store */\n  _a: App | null;\n  /** Effect scope the store is attached to */\n  _e: EffectScope;\n  /** Registry of stores */\n  _s: Map<string, StoreGeneric>;\n}\n\nexport type StoreGeneric = Record<string, any>;\n\nexport interface StorePluginContext {\n  store: Store;\n  app: App;\n}\n\nexport interface StorePlugin {\n  (context: StorePluginContext): void;\n}\n\nexport const storeSymbol: InjectionKey<Store> = Symbol();\n\n/**\n * setActiveStore must be called to handle SSR at the top of functions like\n * `fetch`, `setup`, `serverPrefetch` and others\n */\nexport let activeStore: Store | undefined;\n\nexport const setActiveStore = (store: Store | undefined): Store | undefined =>\n  (activeStore = store);\n\n/**\n * Get the currently active store if there is any.\n */\nexport const getActiveStore = (): Store | undefined => {\n  const store = hasInjectionContext() && inject(storeSymbol, null);\n\n  if (__DEV__ && !store && typeof window === \"undefined\") {\n    console.warn(\n      `[chibivue-store]: Store instance not found in context. This falls back to the global activeStore which exposes you to cross-request state pollution on the server.`,\n    );\n  }\n\n  return store || activeStore;\n};\n"
  },
  {
    "path": "impl/@extensions/chibivue-store/src/store.ts",
    "content": "import {\n  type ComputedRef,\n  computed,\n  hasInjectionContext,\n  inject,\n  isReactive,\n  isRef,\n  markRaw,\n  reactive,\n  toRaw,\n  toRef,\n} from \"chibivue\";\nimport {\n  type StateTree,\n  type Store,\n  type StoreGeneric,\n  activeStore,\n  setActiveStore,\n  storeSymbol,\n} from \"./rootStore\";\n\ntype _GettersTree<S extends StateTree> = Record<string, (state: S) => unknown>;\n\nexport interface StoreDefinition<\n  Id extends string = string,\n  S extends StateTree = StateTree,\n  G extends _GettersTree<S> = _GettersTree<S>,\n  A = Record<string, (...args: any[]) => any>,\n> {\n  (): StoreInstance<Id, S, G, A>;\n}\n\nexport interface StoreInstance<\n  Id extends string = string,\n  S extends StateTree = StateTree,\n  G extends _GettersTree<S> = _GettersTree<S>,\n  A = Record<string, (...args: any[]) => any>,\n> {\n  $id: Id;\n  $state: S;\n  $patch: (partialState: Partial<S> | ((state: S) => void)) => void;\n  $reset: () => void;\n}\n\ninterface StoreOptions<Id extends string, S extends StateTree, G extends _GettersTree<S>, A> {\n  id: Id;\n  state?: () => S;\n  getters?: G & ThisType<S & { [K in keyof G]: ReturnType<G[K]> }>;\n  actions?: A & ThisType<S & A & { [K in keyof G]: ReturnType<G[K]> }>;\n}\n\n// Composition API style (setup function)\nexport function defineStore<Id extends string, SS extends StateTree>(\n  id: Id,\n  setup: () => SS,\n): () => SS;\n\n// Options API style\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(options: StoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>;\n\n// Options API style (id as first argument)\nexport function defineStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(id: Id, options: Omit<StoreOptions<Id, S, G, A>, \"id\">): StoreDefinition<Id, S, G, A>;\n\nexport function defineStore(\n  idOrOptions: string | StoreOptions<string, StateTree, _GettersTree<StateTree>, any>,\n  setupOrOptions?:\n    | (() => StateTree)\n    | Omit<StoreOptions<string, StateTree, _GettersTree<StateTree>, any>, \"id\">,\n): any {\n  let id: string;\n  let setup: (() => StateTree) | undefined;\n  let options:\n    | Omit<StoreOptions<string, StateTree, _GettersTree<StateTree>, any>, \"id\">\n    | undefined;\n\n  const isSetupStore = typeof setupOrOptions === \"function\";\n\n  if (typeof idOrOptions === \"string\") {\n    id = idOrOptions;\n    if (isSetupStore) {\n      setup = setupOrOptions;\n    } else {\n      options = setupOrOptions;\n    }\n  } else {\n    id = idOrOptions.id;\n    options = idOrOptions;\n  }\n\n  function useStore(outerStore?: Store | null): StoreGeneric {\n    const hasContext = hasInjectionContext();\n    // Try to get store from injection context first, then fall back to activeStore\n    outerStore = outerStore || (hasContext ? inject(storeSymbol, null) : null);\n\n    if (outerStore) setActiveStore(outerStore);\n\n    if (__DEV__ && !activeStore) {\n      throw new Error(\n        `[chibivue-store]: \"getActiveStore()\" was called but there was no active Store. ` +\n          `Are you trying to use a store before calling \"app.use(createStore())\"?`,\n      );\n    }\n\n    const store = activeStore!;\n\n    if (!store._s.has(id)) {\n      if (isSetupStore) {\n        createSetupStore(id, setup!, store);\n      } else if (options) {\n        createOptionsStore(id, options, store);\n      }\n    }\n\n    return store._s.get(id)!;\n  }\n\n  useStore.$id = id;\n\n  return useStore;\n}\n\nfunction isComputed<T>(value: ComputedRef<T> | unknown): value is ComputedRef<T> {\n  return !!(isRef(value) && (value as any).effect);\n}\n\nfunction createSetupStore<Id extends string>(id: Id, setup: () => StateTree, store: Store) {\n  // Initialize state for this store in the root state\n  if (!store.state.value[id]) {\n    store.state.value[id] = {};\n  }\n\n  const initialState = store.state.value[id];\n  const setupStore = store._e.run(() => setup())!;\n\n  // Process setup store return values\n  for (const key in setupStore) {\n    const prop = setupStore[key];\n\n    // State: ref or reactive\n    if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {\n      // Hydrate from initial state if exists\n      if (initialState && key in initialState) {\n        if (isRef(prop)) {\n          prop.value = initialState[key];\n        } else {\n          Object.assign(prop, initialState[key]);\n        }\n      }\n      // Sync to root state\n      store.state.value[id][key] = prop;\n    }\n  }\n\n  const _store = reactive({\n    $id: id,\n    ...setupStore,\n    $patch(partialState: Partial<StateTree> | ((state: StateTree) => void)) {\n      if (typeof partialState === \"function\") {\n        partialState(store.state.value[id]);\n      } else {\n        for (const key in partialState) {\n          const value = store.state.value[id][key];\n          if (isRef(value)) {\n            value.value = partialState[key];\n          } else {\n            store.state.value[id][key] = partialState[key];\n          }\n        }\n      }\n    },\n    $reset() {\n      if (__DEV__) {\n        console.warn(\n          `[chibivue-store]: Store \"${id}\" is built using the setup syntax and does not implement $reset().`,\n        );\n      }\n    },\n  }) as StoreGeneric;\n\n  store._s.set(id, _store);\n}\n\nfunction createOptionsStore<\n  Id extends string,\n  S extends StateTree,\n  G extends _GettersTree<S>,\n  A extends Record<string, (...args: any[]) => any>,\n>(id: Id, options: Omit<StoreOptions<Id, S, G, A>, \"id\">, store: Store) {\n  const { state: stateFn, getters, actions } = options;\n\n  // Get initial state from root state (for SSR hydration) or create new\n  const initialState = store.state.value[id] as S | undefined;\n\n  function setup() {\n    // Initialize state in root state if not exists\n    if (!initialState) {\n      store.state.value[id] = stateFn ? stateFn() : {};\n    }\n\n    const localState = toRefs(store.state.value[id]);\n\n    // Create getters as computed\n    const computedGetters: Record<string, ComputedRef<unknown>> = {};\n    if (getters) {\n      for (const key in getters) {\n        const getter = getters[key];\n        computedGetters[key] = markRaw(\n          computed(() => {\n            setActiveStore(store);\n            const _store = store._s.get(id)! as S & { [K in keyof G]: ReturnType<G[K]> };\n            return getter.call(_store, _store);\n          }),\n        );\n      }\n    }\n\n    return {\n      ...localState,\n      ...computedGetters,\n      ...actions,\n    };\n  }\n\n  const setupStore = store._e.run(() => setup())!;\n\n  // Bind actions to store context\n  const boundActions: Record<string, (...args: any[]) => any> = {};\n  if (actions) {\n    for (const key in actions) {\n      const action = actions[key];\n      boundActions[key] = function (this: any, ...args: any[]) {\n        setActiveStore(store);\n        return action.apply(store._s.get(id), args);\n      };\n    }\n  }\n\n  const _store = reactive({\n    $id: id,\n    ...setupStore,\n    ...boundActions,\n    $patch(partialState: Partial<S> | ((state: S) => void)) {\n      if (typeof partialState === \"function\") {\n        partialState(store.state.value[id] as S);\n      } else {\n        mergeReactiveObjects(store.state.value[id], partialState);\n      }\n    },\n    $reset() {\n      const newState = stateFn ? stateFn() : ({} as S);\n      this.$patch(($state: S) => {\n        Object.assign($state, newState);\n      });\n    },\n  }) as StoreGeneric;\n\n  // Define $state as getter/setter\n  Object.defineProperty(_store, \"$state\", {\n    get: () => store.state.value[id],\n    set: (state) => {\n      _store.$patch(($state: S) => {\n        Object.assign($state, state);\n      });\n    },\n  });\n\n  store._s.set(id, _store);\n}\n\nfunction toRefs<T extends StateTree>(obj: T): { [K in keyof T]: any } {\n  const refs = {} as { [K in keyof T]: any };\n  for (const key in obj) {\n    refs[key] = toRef(obj, key);\n  }\n  return refs;\n}\n\nfunction mergeReactiveObjects<T extends Record<any, unknown>>(\n  target: T,\n  patchToApply: Partial<T>,\n): T {\n  for (const key in patchToApply) {\n    if (!Object.prototype.hasOwnProperty.call(patchToApply, key)) continue;\n    const subPatch = patchToApply[key];\n    const targetValue = target[key];\n    if (\n      isPlainObject(targetValue) &&\n      isPlainObject(subPatch) &&\n      Object.prototype.hasOwnProperty.call(target, key) &&\n      !isRef(subPatch) &&\n      !isReactive(subPatch)\n    ) {\n      target[key] = mergeReactiveObjects(targetValue as any, subPatch as any);\n    } else {\n      target[key] = subPatch as T[Extract<keyof T, string>];\n    }\n  }\n  return target;\n}\n\nfunction isPlainObject(obj: unknown): obj is Record<string, unknown> {\n  return (\n    obj !== null &&\n    typeof obj === \"object\" &&\n    Object.prototype.toString.call(obj) === \"[object Object]\"\n  );\n}\n"
  },
  {
    "path": "impl/@extensions/global.d.ts",
    "content": "declare var __DEV__: boolean;\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/package.json",
    "content": "{\n  \"name\": \"vite-plugin-chibivue\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/src/index.ts",
    "content": "import * as _compiler from \"@chibivue/compiler-sfc\";\n\nimport type { Plugin } from \"vite\";\nimport { createFilter } from \"vite\";\nimport { transformMain } from \"./main\";\nimport { parseChibiVueRequest } from \"./utils/query\";\nimport { getDescriptor } from \"./utils/descriptorCache\";\n\nexport interface ResolvedOptions {\n  compiler: typeof _compiler;\n  root: string;\n}\n\nexport default function chibiVuePlugin(): Plugin {\n  const filter = createFilter(/\\.vue$/);\n  const options: ResolvedOptions = {\n    compiler: _compiler,\n    root: process.cwd(),\n  };\n\n  return {\n    name: \"vite:chibivue\",\n\n    // virtual modules\n    resolveId(id) {\n      if (parseChibiVueRequest(id).query.chibivue) return id;\n    },\n    load(id) {\n      const { filename, query } = parseChibiVueRequest(id);\n      if (query.chibivue) {\n        const descriptor = getDescriptor(filename, options)!;\n        if (query.type === \"style\") {\n          const style = descriptor.styles[query.index!];\n          if (query.scoped) {\n            const { code } = options.compiler.compileStyle({\n              source: style.content,\n              filename,\n              id: descriptor.id,\n              scoped: true,\n            });\n            return { code };\n          }\n          return { code: style.content };\n        }\n      }\n    },\n\n    transform(code, id, _) {\n      if (!filter(id)) return;\n      return transformMain(code, id, options);\n    },\n  };\n}\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/src/main.ts",
    "content": "import type { SFCDescriptor } from \"@chibivue/compiler-sfc\";\nimport type { ResolvedOptions } from \".\";\nimport { createDescriptor } from \"./utils/descriptorCache\";\nimport { isUseInlineTemplate, resolveScript } from \"./script\";\nimport { transformTemplateInMain } from \"./template\";\n\nexport async function transformMain(\n  code: string,\n  filename: string,\n  options: ResolvedOptions,\n): Promise<{ code: string }> {\n  const { descriptor } = createDescriptor(filename, code, options);\n\n  // Check if any style is scoped\n  const hasScoped = descriptor.styles.some((s) => s.scoped);\n\n  // script\n  const { code: scriptCode } = genScriptCode(descriptor, options);\n\n  // template\n  const hasTemplateImport = descriptor.template && !isUseInlineTemplate(descriptor);\n\n  let templateCode = \"\";\n\n  if (hasTemplateImport) {\n    const { code } = genTemplateCode(descriptor, options, hasScoped);\n    templateCode = code;\n  }\n\n  const attachedProps: [string, string][] = [];\n  if (templateCode) {\n    attachedProps.push([\"render\", \"_sfc_render\"]);\n  }\n\n  // styles\n  const stylesCode = await genStyleCode(descriptor);\n\n  const output: string[] = [scriptCode, templateCode, stylesCode];\n  output.push(\"\\n\");\n\n  if (attachedProps.length) {\n    output.push(\n      `export default { ..._sfc_main, ${attachedProps.map(([k, v]) => `${k}: ${v}`).join(\", \")} }`,\n    );\n  } else {\n    output.push(`export default _sfc_main`);\n  }\n\n  const resolvedCode = output.join(\"\\n\");\n\n  return { code: resolvedCode };\n}\n\nfunction genScriptCode(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n): {\n  code: string;\n} {\n  let scriptCode = `const _sfc_main = {}`;\n  const script = resolveScript(descriptor, options);\n  if (script) {\n    scriptCode = options.compiler.rewriteDefault(script.content, \"_sfc_main\");\n  }\n\n  return { code: scriptCode };\n}\n\nasync function genStyleCode(descriptor: SFCDescriptor): Promise<string> {\n  let stylesCode = ``;\n\n  for (let i = 0; i < descriptor.styles.length; i++) {\n    const style = descriptor.styles[i];\n    const src = descriptor.filename;\n    const scoped = style.scoped ? \"&scoped=true\" : \"\";\n    const query = `?chibivue&type=style&index=${i}${scoped}&lang.css`;\n    const styleRequest = src + query;\n    stylesCode += `\\nimport ${JSON.stringify(styleRequest)}`;\n  }\n\n  return stylesCode;\n}\n\nfunction genTemplateCode(descriptor: SFCDescriptor, options: ResolvedOptions, hasScoped: boolean) {\n  const template = descriptor.template!;\n  return transformTemplateInMain(template.content.trim(), options, {\n    id: descriptor.id,\n    scoped: hasScoped,\n  });\n}\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/src/script.ts",
    "content": "import type { SFCDescriptor, SFCScriptBlock } from \"@chibivue/compiler-sfc\";\nimport type { ResolvedOptions } from \".\";\n\nexport function resolveScript(\n  descriptor: SFCDescriptor,\n  options: ResolvedOptions,\n): SFCScriptBlock | null {\n  if (!descriptor.script && !descriptor.scriptSetup) return null;\n  let resolved: SFCScriptBlock | null = null;\n  resolved = options.compiler.compileScript(descriptor, {\n    inlineTemplate: isUseInlineTemplate(descriptor),\n  });\n  return resolved;\n}\n\n// Check if we can use compile template as inlined render function\n// inside <script setup>. This can only be done for build because\n// inlined template cannot be individually hot updated.\nexport function isUseInlineTemplate(descriptor: SFCDescriptor): boolean {\n  return !!descriptor.scriptSetup;\n}\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/src/template.ts",
    "content": "import type { SFCTemplateCompileResults } from \"@chibivue/compiler-sfc\";\nimport type { ResolvedOptions } from \".\";\n\nexport interface TemplateOptions {\n  id?: string;\n  scoped?: boolean;\n}\n\nexport function transformTemplateInMain(\n  code: string,\n  options: ResolvedOptions,\n  templateOptions?: TemplateOptions,\n): SFCTemplateCompileResults {\n  const result = compile(code, options, templateOptions);\n  return {\n    ...result,\n    code: result.code.replace(/\\n(function|const) (render|ssrRender)/, \"\\n$1 _sfc_$2\"),\n  };\n}\n\nexport function compile(\n  source: string,\n  options: ResolvedOptions,\n  templateOptions?: TemplateOptions,\n): SFCTemplateCompileResults {\n  return options.compiler.compileTemplate({\n    source,\n    id: templateOptions?.id,\n    scoped: templateOptions?.scoped,\n  });\n}\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/src/utils/descriptorCache.ts",
    "content": "import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport type { SFCDescriptor } from \"@chibivue/compiler-sfc\";\n\nimport type { ResolvedOptions } from \"..\";\n\n// compiler-sfc should be exported so it can be re-used\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nconst cache = new Map<string, SFCDescriptor>();\nconst prevCache = new Map<string, SFCDescriptor | undefined>();\n\nexport function createDescriptor(\n  filename: string,\n  source: string,\n  { root, compiler }: ResolvedOptions,\n): SFCParseResult {\n  const { descriptor } = compiler.parse(source, { filename });\n\n  const normalizedPath = path.normalize(path.relative(root, filename));\n  descriptor.id = getHash(normalizedPath);\n\n  cache.set(filename, descriptor);\n  return { descriptor };\n}\n\nexport function getPrevDescriptor(filename: string): SFCDescriptor | undefined {\n  return prevCache.get(filename);\n}\n\nexport function setPrevDescriptor(filename: string, entry: SFCDescriptor): void {\n  prevCache.set(filename, entry);\n}\n\nexport function getDescriptor(\n  filename: string,\n  options: ResolvedOptions,\n  createIfNotFound = true,\n): SFCDescriptor | undefined {\n  if (cache.has(filename)) {\n    return cache.get(filename)!;\n  }\n  if (createIfNotFound) {\n    const { descriptor } = createDescriptor(filename, fs.readFileSync(filename, \"utf-8\"), options);\n    return descriptor;\n  }\n}\n\nfunction getHash(text: string): string {\n  return createHash(\"sha256\").update(text).digest(\"hex\").substring(0, 8);\n}\n"
  },
  {
    "path": "impl/@extensions/vite-plugin-chibivue/src/utils/query.ts",
    "content": "export interface ChibiVueQuery {\n  chibivue?: boolean;\n  type?: \"script\" | \"template\" | \"style\";\n  index?: number;\n  scoped?: boolean;\n  raw?: boolean;\n  url?: boolean;\n}\n\nexport function parseChibiVueRequest(id: string): {\n  filename: string;\n  query: ChibiVueQuery;\n} {\n  const [filename, rawQuery] = id.split(`?`, 2);\n  const query = Object.fromEntries(new URLSearchParams(rawQuery)) as ChibiVueQuery;\n  if (query.chibivue != null) {\n    query.chibivue = true;\n  }\n  if (query.index != null) {\n    query.index = Number(query.index);\n  }\n  if (query.scoped != null) {\n    query.scoped = true;\n  }\n  if (query.raw != null) {\n    query.raw = true;\n  }\n  if (query.url != null) {\n    query.url = true;\n  }\n  return {\n    filename,\n    query,\n  };\n}\n"
  },
  {
    "path": "impl/@extensions/vscode-chibivue/language-configuration.json",
    "content": "{\n  \"comments\": {\n    \"blockComment\": [\"<!--\", \"-->\"]\n  },\n  \"brackets\": [\n    [\"<!--\", \"-->\"],\n    [\"<\", \">\"],\n    [\"{\", \"}\"],\n    [\"[\", \"]\"],\n    [\"(\", \")\"]\n  ],\n  \"autoClosingPairs\": [\n    { \"open\": \"{\", \"close\": \"}\" },\n    { \"open\": \"[\", \"close\": \"]\" },\n    { \"open\": \"(\", \"close\": \")\" },\n    { \"open\": \"'\", \"close\": \"'\", \"notIn\": [\"string\", \"comment\"] },\n    { \"open\": \"\\\"\", \"close\": \"\\\"\", \"notIn\": [\"string\"] },\n    { \"open\": \"`\", \"close\": \"`\", \"notIn\": [\"string\", \"comment\"] },\n    { \"open\": \"<!--\", \"close\": \"-->\", \"notIn\": [\"comment\", \"string\"] },\n    { \"open\": \"/**\", \"close\": \" */\", \"notIn\": [\"string\"] }\n  ],\n  \"surroundingPairs\": [\n    { \"open\": \"'\", \"close\": \"'\" },\n    { \"open\": \"\\\"\", \"close\": \"\\\"\" },\n    { \"open\": \"`\", \"close\": \"`\" },\n    { \"open\": \"{\", \"close\": \"}\" },\n    { \"open\": \"[\", \"close\": \"]\" },\n    { \"open\": \"(\", \"close\": \")\" },\n    { \"open\": \"<\", \"close\": \">\" }\n  ],\n  \"colorizedBracketPairs\": [\n    [\"{\", \"}\"],\n    [\"[\", \"]\"],\n    [\"(\", \")\"]\n  ],\n  \"folding\": {\n    \"markers\": {\n      \"start\": \"^\\\\s*<!--\\\\s*#region\\\\b.*-->\",\n      \"end\": \"^\\\\s*<!--\\\\s*#endregion\\\\b.*-->\"\n    }\n  },\n  \"indentationRules\": {\n    \"increaseIndentPattern\": \"<(?!\\\\?|(?:area|base|br|col|frame|hr|html|img|input|keygen|link|menuitem|meta|param|source|track|wbr|script|style)\\\\b|[^>]*\\\\/>)([-_\\\\.A-Za-z0-9]+)(?=\\\\s|>)\\\\b[^>]*>(?!.*<\\\\/\\\\1>)|<!--(?!.*-->)|\\\\{[^}\\\"']*$\",\n    \"decreaseIndentPattern\": \"^\\\\s*(<\\\\/[-_\\\\.A-Za-z0-9]+\\\\b[^>]*>|-->|\\\\})\"\n  },\n  \"wordPattern\": \"(-?\\\\d*\\\\.\\\\d\\\\w*)|([^\\\\`\\\\~\\\\!\\\\@\\\\$\\\\^\\\\&\\\\*\\\\(\\\\)\\\\=\\\\+\\\\[\\\\{\\\\]\\\\}\\\\\\\\\\\\|\\\\;\\\\:\\\\'\\\\\\\"\\\\,\\\\.\\\\<\\\\>\\\\/\\\\s]+)\"\n}\n"
  },
  {
    "path": "impl/@extensions/vscode-chibivue/package.json",
    "content": "{\n  \"name\": \"vscode-chibivue\",\n  \"displayName\": \"Chibivue\",\n  \"description\": \"Language support for chibivue SFC files\",\n  \"version\": \"0.0.1\",\n  \"publisher\": \"chibivue\",\n  \"engines\": {\n    \"vscode\": \"^1.75.0\"\n  },\n  \"categories\": [\n    \"Programming Languages\"\n  ],\n  \"activationEvents\": [\n    \"onLanguage:vue\"\n  ],\n  \"main\": \"./dist/extension.js\",\n  \"contributes\": {\n    \"languages\": [\n      {\n        \"id\": \"vue\",\n        \"extensions\": [\n          \".vue\"\n        ],\n        \"configuration\": \"./language-configuration.json\"\n      }\n    ],\n    \"grammars\": [\n      {\n        \"language\": \"vue\",\n        \"scopeName\": \"source.vue\",\n        \"path\": \"./syntaxes/vue.tmLanguage.json\",\n        \"embeddedLanguages\": {\n          \"source.ts\": \"typescript\",\n          \"source.js\": \"javascript\",\n          \"text.html.basic\": \"html\",\n          \"source.css\": \"css\"\n        }\n      }\n    ],\n    \"configuration\": {\n      \"title\": \"Chibivue\",\n      \"properties\": {\n        \"chibivue.trace.server\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"off\",\n            \"messages\",\n            \"verbose\"\n          ],\n          \"default\": \"off\",\n          \"description\": \"Traces the communication between VS Code and the language server.\"\n        }\n      }\n    }\n  },\n  \"scripts\": {\n    \"vscode:prepublish\": \"npm run build\",\n    \"build\": \"esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node\",\n    \"dev\": \"npm run build -- --watch\"\n  },\n  \"dependencies\": {\n    \"@chibivue/language-server\": \"workspace:*\",\n    \"vscode-languageclient\": \"^9.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/vscode\": \"^1.75.0\",\n    \"esbuild\": \"^0.20.0\",\n    \"typescript\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "impl/@extensions/vscode-chibivue/src/extension.ts",
    "content": "import * as vscode from \"vscode\";\nimport * as path from \"path\";\nimport { LanguageClient, TransportKind } from \"vscode-languageclient/node\";\nimport type { LanguageClientOptions, ServerOptions } from \"vscode-languageclient/node\";\n\n// ============================================================================\n// Extension State\n// ============================================================================\n\nlet client: LanguageClient | undefined;\n\n// ============================================================================\n// Extension Activation\n// ============================================================================\n\nexport async function activate(context: vscode.ExtensionContext) {\n  // Find the language server module\n  const serverModule = context.asAbsolutePath(\n    path.join(\"node_modules\", \"@chibivue\", \"language-server\", \"dist\", \"server.js\"),\n  );\n\n  // If the server module doesn't exist, try to find it in workspace\n  const serverPath = await resolveServerPath(context);\n\n  if (!serverPath) {\n    vscode.window.showErrorMessage(\n      \"Chibivue: Could not find language server. Please install @chibivue/language-server.\",\n    );\n    return;\n  }\n\n  // Server options\n  const serverOptions: ServerOptions = {\n    run: {\n      module: serverPath,\n      transport: TransportKind.ipc,\n    },\n    debug: {\n      module: serverPath,\n      transport: TransportKind.ipc,\n      options: {\n        execArgv: [\"--nolazy\", \"--inspect=6009\"],\n      },\n    },\n  };\n\n  // Get TypeScript SDK path\n  const tsdk = await getTsdk();\n\n  // Client options\n  const clientOptions: LanguageClientOptions = {\n    documentSelector: [{ scheme: \"file\", language: \"vue\" }],\n    initializationOptions: {\n      typescript: {\n        tsdk,\n      },\n    },\n    synchronize: {\n      fileEvents: vscode.workspace.createFileSystemWatcher(\"**/*.vue\"),\n    },\n  };\n\n  // Create the language client\n  client = new LanguageClient(\"chibivue\", \"Chibivue Language Server\", serverOptions, clientOptions);\n\n  // Start the client\n  await client.start();\n\n  // Register commands\n  context.subscriptions.push(\n    vscode.commands.registerCommand(\"chibivue.restartServer\", async () => {\n      await client?.restart();\n      vscode.window.showInformationMessage(\"Chibivue: Language server restarted.\");\n    }),\n  );\n\n  vscode.window.showInformationMessage(\"Chibivue: Language support activated.\");\n}\n\n// ============================================================================\n// Extension Deactivation\n// ============================================================================\n\nexport async function deactivate() {\n  if (client) {\n    await client.stop();\n    client = undefined;\n  }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Resolve the path to the language server\n */\nasync function resolveServerPath(context: vscode.ExtensionContext): Promise<string | undefined> {\n  // Try extension's node_modules\n  const extensionServerPath = context.asAbsolutePath(\n    path.join(\"node_modules\", \"@chibivue\", \"language-server\", \"dist\", \"server.js\"),\n  );\n\n  try {\n    await vscode.workspace.fs.stat(vscode.Uri.file(extensionServerPath));\n    return extensionServerPath;\n  } catch {\n    // Not found in extension\n  }\n\n  // Try workspace's node_modules\n  const workspaceFolders = vscode.workspace.workspaceFolders;\n  if (workspaceFolders) {\n    for (const folder of workspaceFolders) {\n      const workspaceServerPath = path.join(\n        folder.uri.fsPath,\n        \"node_modules\",\n        \"@chibivue\",\n        \"language-server\",\n        \"dist\",\n        \"server.js\",\n      );\n\n      try {\n        await vscode.workspace.fs.stat(vscode.Uri.file(workspaceServerPath));\n        return workspaceServerPath;\n      } catch {\n        // Not found in this workspace\n      }\n    }\n  }\n\n  return undefined;\n}\n\n/**\n * Get TypeScript SDK path\n */\nasync function getTsdk(): Promise<string | undefined> {\n  // Try to get from VSCode settings\n  const config = vscode.workspace.getConfiguration(\"typescript\");\n  const tsdk = config.get<string>(\"tsdk\");\n\n  if (tsdk) {\n    return tsdk;\n  }\n\n  // Try workspace's node_modules\n  const workspaceFolders = vscode.workspace.workspaceFolders;\n  if (workspaceFolders) {\n    for (const folder of workspaceFolders) {\n      const tsdkPath = path.join(folder.uri.fsPath, \"node_modules\", \"typescript\", \"lib\");\n\n      try {\n        await vscode.workspace.fs.stat(vscode.Uri.file(tsdkPath));\n        return tsdkPath;\n      } catch {\n        // Not found\n      }\n    }\n  }\n\n  return undefined;\n}\n"
  },
  {
    "path": "impl/@extensions/vscode-chibivue/syntaxes/vue.tmLanguage.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json\",\n  \"name\": \"Vue\",\n  \"scopeName\": \"source.vue\",\n  \"patterns\": [\n    { \"include\": \"#template-tag\" },\n    { \"include\": \"#script-tag\" },\n    { \"include\": \"#style-tag\" }\n  ],\n  \"repository\": {\n    \"template-tag\": {\n      \"begin\": \"(<)(template)(\\\\s[^>]*)?(>)\",\n      \"beginCaptures\": {\n        \"1\": { \"name\": \"punctuation.definition.tag.begin.html\" },\n        \"2\": { \"name\": \"entity.name.tag.html\" },\n        \"3\": { \"patterns\": [{ \"include\": \"#tag-attributes\" }] },\n        \"4\": { \"name\": \"punctuation.definition.tag.end.html\" }\n      },\n      \"end\": \"(</)(template)(>)\",\n      \"endCaptures\": {\n        \"1\": { \"name\": \"punctuation.definition.tag.begin.html\" },\n        \"2\": { \"name\": \"entity.name.tag.html\" },\n        \"3\": { \"name\": \"punctuation.definition.tag.end.html\" }\n      },\n      \"contentName\": \"text.html.basic\",\n      \"patterns\": [\n        { \"include\": \"#vue-interpolation\" },\n        { \"include\": \"#vue-directives\" },\n        { \"include\": \"text.html.basic\" }\n      ]\n    },\n    \"script-tag\": {\n      \"begin\": \"(<)(script)(\\\\s[^>]*)?(>)\",\n      \"beginCaptures\": {\n        \"1\": { \"name\": \"punctuation.definition.tag.begin.html\" },\n        \"2\": { \"name\": \"entity.name.tag.html\" },\n        \"3\": { \"patterns\": [{ \"include\": \"#tag-attributes\" }] },\n        \"4\": { \"name\": \"punctuation.definition.tag.end.html\" }\n      },\n      \"end\": \"(</)(script)(>)\",\n      \"endCaptures\": {\n        \"1\": { \"name\": \"punctuation.definition.tag.begin.html\" },\n        \"2\": { \"name\": \"entity.name.tag.html\" },\n        \"3\": { \"name\": \"punctuation.definition.tag.end.html\" }\n      },\n      \"contentName\": \"source.ts\",\n      \"patterns\": [\n        { \"include\": \"source.ts\" }\n      ]\n    },\n    \"style-tag\": {\n      \"begin\": \"(<)(style)(\\\\s[^>]*)?(>)\",\n      \"beginCaptures\": {\n        \"1\": { \"name\": \"punctuation.definition.tag.begin.html\" },\n        \"2\": { \"name\": \"entity.name.tag.html\" },\n        \"3\": { \"patterns\": [{ \"include\": \"#tag-attributes\" }] },\n        \"4\": { \"name\": \"punctuation.definition.tag.end.html\" }\n      },\n      \"end\": \"(</)(style)(>)\",\n      \"endCaptures\": {\n        \"1\": { \"name\": \"punctuation.definition.tag.begin.html\" },\n        \"2\": { \"name\": \"entity.name.tag.html\" },\n        \"3\": { \"name\": \"punctuation.definition.tag.end.html\" }\n      },\n      \"contentName\": \"source.css\",\n      \"patterns\": [\n        { \"include\": \"source.css\" }\n      ]\n    },\n    \"vue-interpolation\": {\n      \"name\": \"meta.embedded.expression.vue\",\n      \"begin\": \"\\\\{\\\\{\",\n      \"beginCaptures\": {\n        \"0\": { \"name\": \"punctuation.definition.template-expression.begin.vue\" }\n      },\n      \"end\": \"\\\\}\\\\}\",\n      \"endCaptures\": {\n        \"0\": { \"name\": \"punctuation.definition.template-expression.end.vue\" }\n      },\n      \"patterns\": [\n        { \"include\": \"source.ts\" }\n      ]\n    },\n    \"vue-directives\": {\n      \"match\": \"(v-[a-z-]+|@[a-z-]+|:[a-z-]+)(=\\\"[^\\\"]*\\\")?\",\n      \"captures\": {\n        \"1\": { \"name\": \"entity.other.attribute-name.directive.vue\" },\n        \"2\": { \"name\": \"string.quoted.double.html\" }\n      }\n    },\n    \"tag-attributes\": {\n      \"patterns\": [\n        {\n          \"match\": \"(\\\\w+)(=)(\\\"[^\\\"]*\\\")\",\n          \"captures\": {\n            \"1\": { \"name\": \"entity.other.attribute-name.html\" },\n            \"2\": { \"name\": \"punctuation.separator.key-value.html\" },\n            \"3\": { \"name\": \"string.quoted.double.html\" }\n          }\n        },\n        {\n          \"match\": \"\\\\w+\",\n          \"name\": \"entity.other.attribute-name.html\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "impl/@extensions/vscode-chibivue/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"ES2020\"],\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"moduleResolution\": \"node\"\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "impl/chibivue/package.json",
    "content": "{\n  \"name\": \"chibivue\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/chibivue/src/index.ts",
    "content": "import { type RenderFunction, registerRuntimeCompiler } from \"@chibivue/runtime-core\";\nimport * as runtimeDom from \"@chibivue/runtime-dom\";\n\nimport type { CompilerOptions } from \"@chibivue/compiler-core\";\nimport { compile } from \"@chibivue/compiler-dom\";\n\nfunction compileToFunction(template: string, options?: CompilerOptions): RenderFunction {\n  const opts = { ...options, isBrowser: true } as CompilerOptions;\n  const { code } = compile(template, opts);\n  return new Function(\"ChibiVue\", code)(runtimeDom);\n}\n\nregisterRuntimeCompiler(compileToFunction);\n\nexport * from \"@chibivue/runtime-dom\";\nexport * from \"@chibivue/runtime-core\";\n"
  },
  {
    "path": "impl/compiler-core/package.json",
    "content": "{\n  \"name\": \"@chibivue/compiler-core\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/compiler-core/src/ast.ts",
    "content": "import { isString } from \"@chibivue/shared\";\n\nimport {\n  CREATE_ELEMENT_VNODE,\n  CREATE_VNODE,\n  type FRAGMENT,\n  type RENDER_LIST,\n  WITH_DIRECTIVES,\n} from \"./runtimeHelpers\";\nimport type { TransformContext } from \"./transform\";\nimport type { PropsExpression } from \"./transforms/transformElement\";\nimport type { ForParseResult } from \"./transforms/vFor\";\n\nexport const enum NodeTypes {\n  ROOT,\n  ELEMENT,\n  TEXT,\n  COMMENT,\n  INTERPOLATION,\n  SIMPLE_EXPRESSION,\n\n  ATTRIBUTE,\n  DIRECTIVE,\n\n  COMPOUND_EXPRESSION,\n  IF,\n  IF_BRANCH,\n  FOR,\n\n  // codegen\n  VNODE_CALL,\n  JS_CALL_EXPRESSION,\n  JS_OBJECT_EXPRESSION,\n  JS_PROPERTY,\n  JS_ARRAY_EXPRESSION,\n  JS_FUNCTION_EXPRESSION,\n  JS_CONDITIONAL_EXPRESSION,\n\n  // SSR codegen\n  JS_TEMPLATE_LITERAL,\n  JS_IF_STATEMENT,\n  JS_BLOCK_STATEMENT,\n}\n\nexport const enum ElementTypes {\n  ELEMENT,\n  COMPONENT,\n  TEMPLATE,\n}\n\nexport interface SourceLocation {\n  start: Position;\n  end: Position;\n  source: string;\n}\n\nexport interface Node {\n  type: NodeTypes;\n  loc: SourceLocation;\n}\n\nexport interface Position {\n  offset: number; // from start of file\n  line: number;\n  column: number;\n}\n\nexport type ParentNode = RootNode | ElementNode | ForNode | IfBranchNode;\n\nexport type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode;\n\nexport type TemplateChildNode =\n  | ElementNode\n  | TextNode\n  | InterpolationNode\n  | CommentNode\n  | IfNode\n  | IfBranchNode\n  | ForNode;\n\nexport type TemplateTextChildNode = TextNode | InterpolationNode;\n\nexport interface VNodeCall extends Node {\n  type: NodeTypes.VNODE_CALL;\n  tag: string | symbol | CallExpression;\n  props: PropsExpression | undefined;\n  children:\n    | TemplateChildNode[] // multiple children\n    | ForRenderListExpression // v-for\n    | TemplateTextChildNode\n    | SimpleExpressionNode // hoisted\n    | undefined;\n  directives: DirectiveArguments | undefined;\n  isComponent: boolean;\n  isStatic?: boolean;\n}\n\n// JS Node Types ---------------------------------------------------------------\n\n// We also include a number of JavaScript AST nodes for code generation.\n// The AST is an intentionally minimal subset just to meet the exact needs of\n// Vue render function generation.\n\nexport type JSChildNode =\n  | VNodeCall\n  | CallExpression\n  | ObjectExpression\n  | ArrayExpression\n  | ExpressionNode\n  | FunctionExpression\n  | ConditionalExpression;\n\nexport interface CallExpression extends Node {\n  type: NodeTypes.JS_CALL_EXPRESSION;\n  callee: string | symbol;\n  arguments: (string | JSChildNode | TemplateLiteral | TemplateChildNode | TemplateChildNode[])[];\n}\n\nexport interface ObjectExpression extends Node {\n  type: NodeTypes.JS_OBJECT_EXPRESSION;\n  properties: Array<Property>;\n}\nexport interface Property extends Node {\n  type: NodeTypes.JS_PROPERTY;\n  key: ExpressionNode;\n  value: JSChildNode;\n}\n\nexport interface ArrayExpression extends Node {\n  type: NodeTypes.JS_ARRAY_EXPRESSION;\n  elements: Array<string | Node>;\n}\n\nexport interface FunctionExpression extends Node {\n  type: NodeTypes.JS_FUNCTION_EXPRESSION;\n  params: ExpressionNode | string | (ExpressionNode | string)[] | undefined;\n  returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode;\n  body?: BlockStatement | IfStatement;\n  newline: boolean;\n  /**\n   * This flag is for codegen to determine whether it needs to generate the\n   * withScopeId() wrapper\n   */\n  isSlot: boolean;\n  /**\n   * __COMPAT__ only, indicates a slot function that should be excluded from\n   * the legacy $scopedSlots instance property.\n   */\n  isNonScopedSlot?: boolean;\n}\n\nexport interface ConditionalExpression extends Node {\n  type: NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  test: JSChildNode;\n  consequent: JSChildNode;\n  alternate: JSChildNode;\n  newline: boolean;\n}\n\n// SSR types\nexport interface TemplateLiteral extends Node {\n  type: NodeTypes.JS_TEMPLATE_LITERAL;\n  elements: (string | JSChildNode)[];\n}\n\nexport interface IfStatement extends Node {\n  type: NodeTypes.JS_IF_STATEMENT;\n  test: JSChildNode;\n  consequent: BlockStatement;\n  alternate: IfStatement | BlockStatement | undefined;\n}\n\nexport interface BlockStatement extends Node {\n  type: NodeTypes.JS_BLOCK_STATEMENT;\n  body: (JSChildNode | IfStatement)[];\n}\n\nexport interface RootNode extends Node {\n  type: NodeTypes.ROOT;\n  children: TemplateChildNode[];\n  codegenNode?: TemplateChildNode | VNodeCall | BlockStatement;\n  helpers: Set<symbol>;\n  components: string[];\n  hoists: (TemplateChildNode | ExpressionNode)[];\n  // SSR\n  ssrHelpers?: symbol[];\n}\n\nexport type ElementNode = PlainElementNode | ComponentNode | TemplateNode;\n\nexport interface BaseElementNode extends Node {\n  type: NodeTypes.ELEMENT;\n  tag: string;\n  tagType: ElementTypes;\n  props: Array<AttributeNode | DirectiveNode>;\n  children: TemplateChildNode[];\n  isSelfClosing: boolean;\n}\n\nexport interface PlainElementNode extends BaseElementNode {\n  tagType: ElementTypes.ELEMENT;\n  codegenNode: VNodeCall | SimpleExpressionNode | undefined;\n  ssrCodegenNode?: TemplateLiteral;\n}\n\nexport interface TemplateNode extends BaseElementNode {\n  tagType: ElementTypes.TEMPLATE;\n  // TemplateNode is a container type that always gets compiled away\n  codegenNode: undefined;\n}\n\nexport interface TextNode extends Node {\n  type: NodeTypes.TEXT;\n  content: string;\n}\n\nexport interface CommentNode extends Node {\n  type: NodeTypes.COMMENT;\n  content: string;\n}\n\nexport interface InterpolationNode extends Node {\n  type: NodeTypes.INTERPOLATION;\n  content: ExpressionNode;\n}\n\nexport interface ComponentNode extends BaseElementNode {\n  tagType: ElementTypes.COMPONENT;\n  codegenNode: VNodeCall | undefined;\n}\n\nexport interface SimpleExpressionNode extends Node {\n  type: NodeTypes.SIMPLE_EXPRESSION;\n  content: string;\n  isStatic: boolean;\n  identifiers?: string[];\n}\n\nexport interface CompoundExpressionNode extends Node {\n  type: NodeTypes.COMPOUND_EXPRESSION;\n  children: (\n    | SimpleExpressionNode\n    | CompoundExpressionNode\n    | InterpolationNode\n    | TextNode\n    | string\n    | symbol\n  )[];\n\n  /**\n   * an expression parsed as the params of a function will track\n   * the identifiers declared inside the function body.\n   */\n  identifiers?: string[];\n  isHandlerKey?: boolean;\n}\n\nexport interface IfNode extends Node {\n  type: NodeTypes.IF;\n  branches: IfBranchNode[];\n  codegenNode?: IfConditionalExpression;\n}\n\nexport interface IfConditionalExpression extends ConditionalExpression {\n  consequent: VNodeCall;\n  alternate: VNodeCall | IfConditionalExpression;\n}\n\nexport interface IfBranchNode extends Node {\n  type: NodeTypes.IF_BRANCH;\n  condition: ExpressionNode | undefined; // else\n  children: TemplateChildNode[];\n  userKey?: AttributeNode | DirectiveNode;\n}\n\nexport interface ForNode extends Node {\n  type: NodeTypes.FOR;\n  source: ExpressionNode;\n  valueAlias: ExpressionNode | undefined;\n  keyAlias: ExpressionNode | undefined;\n  children: TemplateChildNode[];\n  parseResult: ForParseResult;\n  codegenNode?: ForCodegenNode;\n}\n\nexport interface AttributeNode extends Node {\n  type: NodeTypes.ATTRIBUTE;\n  name: string;\n  value: TextNode | undefined;\n}\n\nexport interface DirectiveNode extends Node {\n  type: NodeTypes.DIRECTIVE;\n  name: string;\n  exp: ExpressionNode | undefined;\n  arg: ExpressionNode | undefined;\n  modifiers: string[];\n}\n\n// Codegen Node Types ----------------------------------------------------------\n\nexport interface DirectiveArguments extends ArrayExpression {\n  elements: DirectiveArgumentNode[];\n}\n\nexport interface DirectiveArgumentNode extends ArrayExpression {\n  elements: // dir, exp, arg, modifiers\n    | [string]\n    | [string, ExpressionNode]\n    | [string, ExpressionNode, ExpressionNode]\n    | [string, ExpressionNode, ExpressionNode, ObjectExpression];\n}\n\nexport interface ForCodegenNode extends VNodeCall {\n  isBlock: true;\n  tag: typeof FRAGMENT;\n  props: undefined;\n  children: ForRenderListExpression;\n  patchFlag: string;\n  disableTracking: boolean;\n}\n\nexport interface ForRenderListExpression extends CallExpression {\n  callee: typeof RENDER_LIST;\n  arguments: [ExpressionNode, ForIteratorExpression];\n}\n\nexport interface ForIteratorExpression extends FunctionExpression {\n  returns: VNodeCall;\n}\n\n// AST Utilities ---------------------------------------------------------------\n\nexport const locStub: SourceLocation = {\n  source: \"\",\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n};\n\nexport function createRoot(children: TemplateChildNode[], loc: SourceLocation = locStub): RootNode {\n  return {\n    type: NodeTypes.ROOT,\n    children,\n    components: [],\n    helpers: new Set(),\n    hoists: [],\n    codegenNode: undefined,\n    loc,\n  };\n}\n\nexport function createVNodeCall(\n  context: TransformContext | null,\n  tag: VNodeCall[\"tag\"],\n  props?: VNodeCall[\"props\"],\n  children?: VNodeCall[\"children\"],\n  directives?: VNodeCall[\"directives\"],\n  isComponent: VNodeCall[\"isComponent\"] = false,\n  loc: SourceLocation = locStub,\n): VNodeCall {\n  if (context) {\n    context.helper(getVNodeHelper(isComponent));\n    if (directives) {\n      context.helper(WITH_DIRECTIVES);\n    }\n  }\n\n  return {\n    type: NodeTypes.VNODE_CALL,\n    tag,\n    props,\n    children,\n    directives,\n    isComponent,\n    loc,\n  };\n}\n\nexport function createArrayExpression(\n  elements: ArrayExpression[\"elements\"],\n  loc: SourceLocation = locStub,\n): ArrayExpression {\n  return {\n    type: NodeTypes.JS_ARRAY_EXPRESSION,\n    elements,\n    loc,\n  };\n}\n\nexport function createObjectExpression(\n  properties: ObjectExpression[\"properties\"],\n  loc: SourceLocation = locStub,\n): ObjectExpression {\n  return {\n    type: NodeTypes.JS_OBJECT_EXPRESSION,\n    properties,\n    loc,\n  };\n}\n\nexport function createObjectProperty(\n  key: Property[\"key\"] | string,\n  value: Property[\"value\"],\n  loc: SourceLocation = locStub,\n): Property {\n  return {\n    type: NodeTypes.JS_PROPERTY,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value,\n    loc,\n  };\n}\n\nexport function createSimpleExpression(\n  content: SimpleExpressionNode[\"content\"],\n  isStatic: SimpleExpressionNode[\"isStatic\"] = false,\n  loc: SourceLocation = locStub,\n): SimpleExpressionNode {\n  return {\n    type: NodeTypes.SIMPLE_EXPRESSION,\n    isStatic,\n    content,\n    loc,\n  };\n}\n\nexport function createCompoundExpression(\n  children: CompoundExpressionNode[\"children\"],\n  loc: SourceLocation = locStub,\n): CompoundExpressionNode {\n  return {\n    type: NodeTypes.COMPOUND_EXPRESSION,\n    children,\n    loc,\n  };\n}\n\nexport function createCallExpression<T extends CallExpression[\"callee\"]>(\n  callee: T,\n  args: CallExpression[\"arguments\"] = [],\n  loc: SourceLocation = locStub,\n): CallExpression {\n  return {\n    type: NodeTypes.JS_CALL_EXPRESSION,\n    loc,\n    callee,\n    arguments: args,\n  } as CallExpression;\n}\n\nexport function createFunctionExpression(\n  params: FunctionExpression[\"params\"],\n  returns: FunctionExpression[\"returns\"] = undefined,\n  newline: boolean = false,\n  isSlot: boolean = false,\n  loc: SourceLocation = locStub,\n): FunctionExpression {\n  return {\n    type: NodeTypes.JS_FUNCTION_EXPRESSION,\n    params,\n    returns,\n    newline,\n    isSlot,\n    loc,\n  };\n}\n\nexport function getVNodeHelper(\n  isComponent: boolean,\n): typeof CREATE_VNODE | typeof CREATE_ELEMENT_VNODE {\n  return isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE;\n}\n\nexport function createConditionalExpression(\n  test: ConditionalExpression[\"test\"],\n  consequent: ConditionalExpression[\"consequent\"],\n  alternate: ConditionalExpression[\"alternate\"],\n  newline = true,\n): ConditionalExpression {\n  return {\n    type: NodeTypes.JS_CONDITIONAL_EXPRESSION,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub,\n  };\n}\n\n// SSR factories\nexport function createTemplateLiteral(elements: TemplateLiteral[\"elements\"]): TemplateLiteral {\n  return {\n    type: NodeTypes.JS_TEMPLATE_LITERAL,\n    elements,\n    loc: locStub,\n  };\n}\n\nexport function createBlockStatement(body: BlockStatement[\"body\"]): BlockStatement {\n  return {\n    type: NodeTypes.JS_BLOCK_STATEMENT,\n    body,\n    loc: locStub,\n  };\n}\n\nexport function createIfStatement(\n  test: IfStatement[\"test\"],\n  consequent: IfStatement[\"consequent\"],\n  alternate?: IfStatement[\"alternate\"],\n): IfStatement {\n  return {\n    type: NodeTypes.JS_IF_STATEMENT,\n    test,\n    consequent,\n    alternate,\n    loc: locStub,\n  };\n}\n"
  },
  {
    "path": "impl/compiler-core/src/babelUtils.ts",
    "content": "import type {\n  Function,\n  Identifier,\n  ImportDefaultSpecifier,\n  ImportNamespaceSpecifier,\n  ImportSpecifier,\n  Node,\n} from \"@babel/types\";\n\nimport { walk } from \"estree-walker\";\n\nexport function walkIdentifiers(\n  root: Node,\n  onIdentifier: (node: Identifier) => void,\n  knownIds: Record<string, number> = Object.create(null),\n  parentStack: Node[] = [],\n): void {\n  (walk as any)(root, {\n    enter(node: Node, parent: Node | undefined) {\n      parent && parentStack.push(parent);\n      if (node.type === \"Identifier\") {\n        const isLocal = !!knownIds[node.name];\n        const isRefed = isReferencedIdentifier(node, parent!, parentStack);\n        if (!isLocal && isRefed) {\n          onIdentifier(node);\n        }\n      } else if (isFunctionType(node)) {\n        walkFunctionParams(node, (id) => markScopeIdentifier(node, id, knownIds));\n      }\n    },\n    leave(_node: Node, parent: Node | undefined) {\n      parent && parentStack.pop();\n    },\n  });\n}\n\nexport function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void): void {\n  for (const p of node.params) {\n    for (const id of extractIdentifiers(p)) {\n      onIdent(id);\n    }\n  }\n}\n\nexport function extractIdentifiers(param: Node, nodes: Identifier[] = []): Identifier[] {\n  switch (param.type) {\n    case \"Identifier\":\n      nodes.push(param);\n      break;\n\n    case \"MemberExpression\":\n      let object: any = param;\n      while (object.type === \"MemberExpression\") {\n        object = object.object;\n      }\n      nodes.push(object);\n      break;\n\n    case \"ObjectPattern\":\n      for (const prop of param.properties) {\n        if (prop.type === \"RestElement\") {\n          extractIdentifiers(prop.argument, nodes);\n        } else {\n          extractIdentifiers(prop.value, nodes);\n        }\n      }\n      break;\n\n    case \"ArrayPattern\":\n      param.elements.forEach((element) => {\n        if (element) extractIdentifiers(element, nodes);\n      });\n      break;\n\n    case \"RestElement\":\n      extractIdentifiers(param.argument, nodes);\n      break;\n\n    case \"AssignmentPattern\":\n      extractIdentifiers(param.left, nodes);\n      break;\n  }\n\n  return nodes;\n}\n\nfunction markScopeIdentifier(\n  node: Node & { scopeIds?: Set<string> },\n  child: Identifier,\n  knownIds: Record<string, number>,\n) {\n  const { name } = child;\n  if (node.scopeIds && node.scopeIds.has(name)) {\n    return;\n  }\n  if (name in knownIds) {\n    knownIds[name]++;\n  } else {\n    knownIds[name] = 1;\n  }\n  (node.scopeIds || (node.scopeIds = new Set())).add(name);\n}\n\nexport const isFunctionType = (node: Node): node is Function => {\n  return /Function(?:Expression|Declaration)$|Method$/.test(node.type);\n};\n\nexport function getImportedName(\n  specifier: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier,\n): string {\n  if (specifier.type === \"ImportSpecifier\")\n    return specifier.imported.type === \"Identifier\"\n      ? specifier.imported.name\n      : specifier.imported.value;\n  else if (specifier.type === \"ImportNamespaceSpecifier\") return \"*\";\n  return \"default\";\n}\n\nexport function isReferencedIdentifier(\n  id: Identifier,\n  parent: Node | null,\n  parentStack: Node[],\n): boolean {\n  if (!parent) {\n    return true;\n  }\n\n  // is a special keyword but parsed as identifier\n  if (id.name === \"arguments\") {\n    return false;\n  }\n\n  if (isReferenced(id, parent)) {\n    return true;\n  }\n\n  // babel's isReferenced check returns false for ids being assigned to, so we\n  // need to cover those cases here\n  switch (parent.type) {\n    case \"AssignmentExpression\":\n    case \"AssignmentPattern\":\n      return true;\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return isInDestructureAssignment(parent, parentStack);\n  }\n\n  return false;\n}\n\nexport function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {\n  if (parent && (parent.type === \"ObjectProperty\" || parent.type === \"ArrayPattern\")) {\n    let i = parentStack.length;\n    while (i--) {\n      const p = parentStack[i];\n      if (p.type === \"AssignmentExpression\") {\n        return true;\n      } else if (p.type !== \"ObjectProperty\" && !p.type.endsWith(\"Pattern\")) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts\n * To avoid runtime dependency on @babel/types (which includes process references)\n * This file should not change very often in babel but we may need to keep it\n * up-to-date from time to time.\n *\n * https://github.com/babel/babel/blob/main/LICENSE\n *\n */\nfunction isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {\n  switch (parent.type) {\n    // yes: PARENT[NODE]\n    // yes: NODE.child\n    // no: parent.NODE\n    case \"MemberExpression\":\n    case \"OptionalMemberExpression\":\n      if (parent.property === node) {\n        return !!parent.computed;\n      }\n      return parent.object === node;\n\n    case \"JSXMemberExpression\":\n      return parent.object === node;\n    // no: let NODE = init;\n    // yes: let id = NODE;\n    case \"VariableDeclarator\":\n      return parent.init === node;\n\n    // yes: () => NODE\n    // no: (NODE) => {}\n    case \"ArrowFunctionExpression\":\n      return parent.body === node;\n\n    // no: class { #NODE; }\n    // no: class { get #NODE() {} }\n    // no: class { #NODE() {} }\n    // no: class { fn() { return this.#NODE; } }\n    case \"PrivateName\":\n      return false;\n\n    // no: class { NODE() {} }\n    // yes: class { [NODE]() {} }\n    // no: class { foo(NODE) {} }\n    case \"ClassMethod\":\n    case \"ClassPrivateMethod\":\n    case \"ObjectMethod\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return false;\n\n    // yes: { [NODE]: \"\" }\n    // no: { NODE: \"\" }\n    // depends: { NODE }\n    // depends: { key: NODE }\n    case \"ObjectProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      // parent.value === node\n      return !grandparent || grandparent.type !== \"ObjectPattern\";\n    // no: class { NODE = value; }\n    // yes: class { [NODE] = value; }\n    // yes: class { key = NODE; }\n    case \"ClassProperty\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n      return true;\n    case \"ClassPrivateProperty\":\n      return parent.key !== node;\n\n    // no: class NODE {}\n    // yes: class Foo extends NODE {}\n    case \"ClassDeclaration\":\n    case \"ClassExpression\":\n      return parent.superClass === node;\n\n    // yes: left = NODE;\n    // no: NODE = right;\n    case \"AssignmentExpression\":\n      return parent.right === node;\n\n    // no: [NODE = foo] = [];\n    // yes: [foo = NODE] = [];\n    case \"AssignmentPattern\":\n      return parent.right === node;\n\n    // no: NODE: for (;;) {}\n    case \"LabeledStatement\":\n      return false;\n\n    // no: try {} catch (NODE) {}\n    case \"CatchClause\":\n      return false;\n\n    // no: function foo(...NODE) {}\n    case \"RestElement\":\n      return false;\n\n    case \"BreakStatement\":\n    case \"ContinueStatement\":\n      return false;\n\n    // no: function NODE() {}\n    // no: function foo(NODE) {}\n    case \"FunctionDeclaration\":\n    case \"FunctionExpression\":\n      return false;\n\n    // no: export NODE from \"foo\";\n    // no: export * as NODE from \"foo\";\n    case \"ExportNamespaceSpecifier\":\n    case \"ExportDefaultSpecifier\":\n      return false;\n\n    // no: export { foo as NODE };\n    // yes: export { NODE as foo };\n    // no: export { NODE as foo } from \"foo\";\n    case \"ExportSpecifier\":\n      // @ts-expect-error\n      if (grandparent?.source) {\n        return false;\n      }\n      return parent.local === node;\n\n    // no: import NODE from \"foo\";\n    // no: import * as NODE from \"foo\";\n    // no: import { NODE as foo } from \"foo\";\n    // no: import { foo as NODE } from \"foo\";\n    // no: import NODE from \"bar\";\n    case \"ImportDefaultSpecifier\":\n    case \"ImportNamespaceSpecifier\":\n    case \"ImportSpecifier\":\n      return false;\n\n    // no: import \"foo\" assert { NODE: \"json\" }\n    case \"ImportAttribute\":\n      return false;\n\n    // no: <div NODE=\"foo\" />\n    case \"JSXAttribute\":\n      return false;\n\n    // no: [NODE] = [];\n    // no: ({ NODE }) = [];\n    case \"ObjectPattern\":\n    case \"ArrayPattern\":\n      return false;\n\n    // no: new.NODE\n    // no: NODE.target\n    case \"MetaProperty\":\n      return false;\n\n    // yes: type X = { someProperty: NODE }\n    // no: type X = { NODE: OtherType }\n    case \"ObjectTypeProperty\":\n      return parent.key !== node;\n\n    // yes: enum X { Foo = NODE }\n    // no: enum X { NODE }\n    case \"TSEnumMember\":\n      return parent.id !== node;\n\n    // yes: { [NODE]: value }\n    // no: { NODE: value }\n    case \"TSPropertySignature\":\n      if (parent.key === node) {\n        return !!parent.computed;\n      }\n\n      return true;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "impl/compiler-core/src/codegen.ts",
    "content": "import { isArray, isString, isSymbol } from \"@chibivue/shared\";\nimport {\n  type ArrayExpression,\n  type BlockStatement,\n  type CallExpression,\n  type CommentNode,\n  type CompoundExpressionNode,\n  type ConditionalExpression,\n  type ExpressionNode,\n  type FunctionExpression,\n  type IfStatement,\n  type InterpolationNode,\n  type JSChildNode,\n  NodeTypes,\n  type ObjectExpression,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  type TemplateLiteral,\n  type TextNode,\n  type VNodeCall,\n} from \"./ast\";\nimport type { CodegenOptions } from \"./options\";\nimport {\n  CREATE_COMMENT,\n  CREATE_ELEMENT_VNODE,\n  RESOLVE_COMPONENT,\n  TO_DISPLAY_STRING,\n  WITH_DIRECTIVES,\n  helperNameMap,\n} from \"./runtimeHelpers\";\nimport { toValidAssetId } from \"./transforms/transformElement\";\n\nconst aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\n\nexport interface CodegenResult {\n  code: string;\n  preamble: string;\n  ast: RootNode;\n}\n\ntype CodegenNode = TemplateChildNode | JSChildNode | TemplateLiteral | IfStatement | BlockStatement;\n\nexport interface CodegenContext {\n  source: string;\n  code: string;\n  line: number;\n  column: number;\n  offset: number;\n  indentLevel: number;\n  runtimeGlobalName: string;\n  runtimeModuleName: string;\n  inline?: boolean;\n  scopeId?: string;\n  ssr?: boolean;\n  helper(key: symbol): string;\n  push(code: string, node?: CodegenNode): void;\n  indent(): void;\n  deindent(withoutNewLine?: boolean): void;\n  newline(): void;\n  isBrowser: boolean;\n}\n\nfunction createCodegenContext(\n  ast: RootNode,\n  { isBrowser = false, scopeId, ssr = false }: CodegenOptions,\n): CodegenContext {\n  const context: CodegenContext = {\n    source: ast.loc.source,\n    code: ``,\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    runtimeGlobalName: `ChibiVue`,\n    runtimeModuleName: \"chibivue\",\n    isBrowser,\n    scopeId,\n    ssr,\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    },\n  };\n\n  function newline(n: number) {\n    context.push(\"\\n\" + `  `.repeat(n));\n  }\n\n  return context;\n}\n\nexport function generate(ast: RootNode, options: CodegenOptions): CodegenResult {\n  const context = createCodegenContext(ast, {\n    isBrowser: options.isBrowser,\n    scopeId: options.scopeId,\n    ssr: options.ssr,\n  });\n  const { push } = context;\n  const isSetupInlined = !options.isBrowser && !!options.inline;\n  const ssr = !!options.ssr;\n\n  const preambleContext = isSetupInlined ? createCodegenContext(ast, options) : context;\n\n  genFunctionPreamble(ast, preambleContext);\n\n  // generate hoisted nodes\n  if (ast.hoists.length) {\n    genHoists(ast.hoists, preambleContext);\n    preambleContext.newline();\n  }\n\n  const functionName = ssr ? `ssrRender` : `render`;\n  const args = ssr ? [\"_ctx\", \"_push\", \"_parent\", \"_attrs\"] : [\"_ctx\"];\n  const signature = args.join(\", \");\n\n  push(`function ${functionName}(${signature}) { `);\n  context.indent();\n\n  // generate asset resolution statements\n  if (ast.components.length) {\n    genAssets(ast.components, context);\n    context.newline();\n  }\n\n  if (!ssr) {\n    push(`return `);\n  }\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context);\n  } else if (!ssr) {\n    push(`null`);\n  }\n\n  context.deindent();\n  push(` }`);\n\n  return {\n    ast,\n    preamble: isSetupInlined ? preambleContext.code : ``,\n    code: context.code,\n  };\n}\n\nfunction genFunctionPreamble(ast: RootNode, context: CodegenContext) {\n  const { push, newline, runtimeGlobalName, runtimeModuleName, isBrowser, ssr } = context;\n\n  if (isBrowser) {\n    push(`const _ChibiVue = ${runtimeGlobalName}\\n`);\n  } else {\n    push(`import * as _ChibiVue from '${runtimeModuleName}'\\n`);\n    if (ssr && ast.ssrHelpers?.length) {\n      push(\n        `import { ${ast.ssrHelpers.map(aliasHelper).join(\", \")} } from '${runtimeModuleName}/server-renderer'\\n`,\n      );\n    }\n  }\n\n  const helpers = Array.from(ast.helpers);\n  if (helpers.length) {\n    push(`const { ${helpers.map(aliasHelper).join(\", \")} } = _ChibiVue\\n`);\n  }\n  newline();\n  if (isBrowser) push(`return `);\n}\n\nfunction genNode(node: CodegenNode | symbol | string, context: CodegenContext) {\n  if (isString(node)) {\n    context.push(node);\n    return;\n  }\n\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n    case NodeTypes.IF:\n    case NodeTypes.FOR: {\n      genNode(node.codegenNode!, context);\n      break;\n    }\n    case NodeTypes.TEXT:\n      genText(node, context);\n      break;\n    case NodeTypes.SIMPLE_EXPRESSION:\n      genExpression(node, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      genInterpolation(node, context);\n      break;\n    case NodeTypes.VNODE_CALL:\n      genVNodeCall(node, context);\n      break;\n    case NodeTypes.COMPOUND_EXPRESSION:\n      genCompoundExpression(node, context);\n      break;\n    case NodeTypes.COMMENT:\n      genComment(node, context);\n      break;\n    case NodeTypes.JS_CALL_EXPRESSION:\n      genCallExpression(node, context);\n      break;\n    case NodeTypes.JS_OBJECT_EXPRESSION:\n      genObjectExpression(node, context);\n      break;\n    case NodeTypes.JS_ARRAY_EXPRESSION:\n      genArrayExpression(node, context);\n      break;\n    case NodeTypes.JS_FUNCTION_EXPRESSION:\n      genFunctionExpression(node, context);\n      break;\n    case NodeTypes.JS_CONDITIONAL_EXPRESSION:\n      genConditionalExpression(node, context);\n      break;\n    // SSR\n    case NodeTypes.JS_TEMPLATE_LITERAL:\n      genTemplateLiteral(node, context);\n      break;\n    case NodeTypes.JS_IF_STATEMENT:\n      genIfStatement(node, context);\n      break;\n    case NodeTypes.JS_BLOCK_STATEMENT:\n      genBlockStatement(node, context);\n      break;\n    /* istanbul ignore next */\n    case NodeTypes.IF_BRANCH:\n      // noop\n      break;\n    default: {\n      // make sure we exhaust all possible types\n      const exhaustiveCheck: never = node;\n      return exhaustiveCheck;\n    }\n  }\n}\n\nfunction genText(node: TextNode, context: CodegenContext) {\n  context.push(JSON.stringify(node.content), node);\n}\n\nfunction genExpression(node: SimpleExpressionNode, context: CodegenContext) {\n  const { content, isStatic } = node;\n  context.push(isStatic ? JSON.stringify(content) : content, node);\n}\n\nfunction genInterpolation(node: InterpolationNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(TO_DISPLAY_STRING)}(`);\n  genNode(node.content, context);\n  push(`)`);\n}\n\nfunction genCompoundExpression(node: CompoundExpressionNode, context: CodegenContext) {\n  for (let i = 0; i < node.children!.length; i++) {\n    const child = node.children![i];\n    if (isString(child)) {\n      context.push(child);\n    } else {\n      genNode(child, context);\n    }\n  }\n}\n\nfunction genExpressionAsPropertyKey(node: ExpressionNode, context: CodegenContext) {\n  const { push } = context;\n  if (node.type === NodeTypes.COMPOUND_EXPRESSION) {\n    push(`[`);\n    genCompoundExpression(node, context);\n    push(`]`);\n  } else if (node.isStatic) {\n    push(JSON.stringify(node.content), node);\n  } else {\n    push(`[${node.content}]`, node);\n  }\n}\n\nfunction genComment(node: CommentNode, context: CodegenContext) {\n  const { push, helper } = context;\n  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);\n}\n\nfunction genVNodeCall(node: VNodeCall, context: CodegenContext) {\n  const { push, helper, scopeId } = context;\n  const { tag, props, children, directives } = node;\n  if (directives) {\n    push(helper(WITH_DIRECTIVES) + `(`);\n  }\n  push(helper(CREATE_ELEMENT_VNODE) + `(`, node);\n\n  // Add scopeId to props if present\n  let propsWithScope = props;\n  if (scopeId) {\n    const scopeIdProp = `\"data-v-${scopeId}\": \"\"`;\n    if (props) {\n      // Merge with existing props\n      propsWithScope = `{ ...${genPropsString(props, context)}, ${scopeIdProp} }` as any;\n    } else {\n      propsWithScope = `{ ${scopeIdProp} }` as any;\n    }\n    genNodeList(genNullableArgs([tag, propsWithScope, children]), context);\n  } else {\n    genNodeList(genNullableArgs([tag, props, children]), context);\n  }\n\n  push(`)`);\n  if (directives) {\n    push(`, `);\n    genNode(directives, context);\n    push(`)`);\n  }\n}\n\nfunction genPropsString(props: VNodeCall[\"props\"], context: CodegenContext): string {\n  if (!props) return \"{}\";\n  if (typeof props === \"string\") return props;\n\n  // Generate the props to a temporary string\n  const tempContext: CodegenContext = {\n    ...context,\n    code: \"\",\n    push(code: string) {\n      tempContext.code += code;\n    },\n  };\n  genNode(props, tempContext);\n  return tempContext.code;\n}\n\nfunction genNullableArgs(args: any[]): CallExpression[\"arguments\"] {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\n\n// JavaScript\nfunction genCallExpression(node: CallExpression, context: CodegenContext) {\n  const { push, helper } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  push(callee + `(`, node);\n  genNodeList(node.arguments, context);\n  push(`)`);\n}\n\nfunction genObjectExpression(node: ObjectExpression, context: CodegenContext) {\n  const { push } = context;\n  const { properties } = node;\n\n  if (!properties.length) {\n    push(`{}`, node);\n    return;\n  }\n\n  push(`{ `);\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    // key\n    genExpressionAsPropertyKey(key, context);\n    push(`: `);\n    // value\n    genNode(value, context);\n    if (i < properties.length - 1) {\n      // will only reach this if it's multilines\n      push(`,`);\n    }\n  }\n  push(` }`);\n}\n\nfunction genArrayExpression(node: ArrayExpression, context: CodegenContext) {\n  genNodeListAsArray(node.elements as CodegenNode[], context);\n}\n\nfunction genFunctionExpression(node: FunctionExpression, context: CodegenContext) {\n  const { push, indent, deindent } = context;\n  const { params, returns, body, newline } = node;\n\n  push(`(`, node);\n  if (isArray(params)) {\n    genNodeList(params, context);\n  } else if (params) {\n    genNode(params, context);\n  }\n  push(`) => `);\n  if (newline || body) {\n    push(`{`);\n    indent();\n  }\n  if (body) {\n    genNode(body, context);\n  } else if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context);\n    } else {\n      genNode(returns, context);\n    }\n  }\n  if (newline || body) {\n    deindent();\n    push(`}`);\n  }\n}\n\nfunction genConditionalExpression(node: ConditionalExpression, context: CodegenContext) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === NodeTypes.SIMPLE_EXPRESSION) {\n    genExpression(test, context);\n  } else {\n    push(`(`);\n    genNode(test, context);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(true /* without newline */);\n}\n\nfunction genNodeListAsArray(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n) {\n  context.push(`[`);\n  genNodeList(nodes, context);\n  context.push(`]`);\n}\n\nfunction genNodeList(\n  nodes: (string | CodegenNode | TemplateChildNode[])[],\n  context: CodegenContext,\n  comma: boolean = true,\n) {\n  const { push } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context);\n    } else {\n      genNode(node, context);\n    }\n\n    if (i < nodes.length - 1) {\n      comma && push(\", \");\n    }\n  }\n}\n\nfunction genAssets(assets: string[], { helper, push, newline }: CodegenContext) {\n  const resolver = helper(RESOLVE_COMPONENT);\n  for (let i = 0; i < assets.length; i++) {\n    let id = assets[i];\n    const maybeSelfReference = id.endsWith(\"__self\");\n    if (maybeSelfReference) {\n      id = id.slice(0, -6);\n    }\n    push(\n      `const ${toValidAssetId(id, \"component\")} = ${resolver}(${JSON.stringify(\n        id,\n      )}${maybeSelfReference ? `, true` : ``})`,\n    );\n    if (i < assets.length - 1) {\n      newline();\n    }\n  }\n}\n\nfunction genHoists(hoists: (TemplateChildNode | ExpressionNode)[], context: CodegenContext) {\n  const { push, newline } = context;\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(`const _hoisted_${i + 1} = `);\n      genNode(exp, context);\n      newline();\n    }\n  }\n}\n\n// SSR codegen\nfunction genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {\n  const { push, indent, deindent } = context;\n  push(\"`\");\n  const l = node.elements.length;\n  const multilines = l > 3;\n  for (let i = 0; i < l; i++) {\n    const e = node.elements[i];\n    if (isString(e)) {\n      push(e.replace(/(`|\\$|\\\\)/g, \"\\\\$1\"));\n    } else {\n      push(\"${\");\n      if (multilines) indent();\n      genNode(e, context);\n      if (multilines) deindent();\n      push(\"}\");\n    }\n  }\n  push(\"`\");\n}\n\nfunction genIfStatement(node: IfStatement, context: CodegenContext) {\n  const { push, indent, deindent } = context;\n  const { test, consequent, alternate } = node;\n  push(`if (`);\n  genNode(test, context);\n  push(`) {`);\n  indent();\n  genNode(consequent, context);\n  deindent();\n  push(`}`);\n  if (alternate) {\n    push(` else `);\n    if (alternate.type === NodeTypes.JS_IF_STATEMENT) {\n      genIfStatement(alternate, context);\n    } else {\n      push(`{`);\n      indent();\n      genNode(alternate, context);\n      deindent();\n      push(`}`);\n    }\n  }\n}\n\nfunction genBlockStatement(node: BlockStatement, context: CodegenContext) {\n  const { push, indent, deindent, newline } = context;\n  for (let i = 0; i < node.body.length; i++) {\n    genNode(node.body[i], context);\n    if (i < node.body.length - 1) {\n      newline();\n    }\n  }\n}\n"
  },
  {
    "path": "impl/compiler-core/src/compile.ts",
    "content": "import { isString } from \"@chibivue/shared\";\n\nimport type { RootNode } from \"./ast\";\nimport { type CodegenResult, generate } from \"./codegen\";\nimport { baseParse } from \"./parse\";\nimport { type DirectiveTransform, type NodeTransform, transform } from \"./transform\";\n\nimport { transformElement } from \"./transforms/transformElement\";\nimport { transformExpression } from \"./transforms/transformExpression\";\nimport { transformFor } from \"./transforms/vFor\";\n\nimport { transformBind } from \"./transforms/vBind\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformModel } from \"./transforms/vModel\";\nimport type { CompilerOptions } from \"./options\";\nimport { transformIf } from \"./transforms/vIf\";\n\nexport type TransformPreset = [NodeTransform[], Record<string, DirectiveTransform>];\n\nexport function getBaseTransformPreset(): TransformPreset {\n  return [\n    [transformIf, transformFor, transformExpression, transformElement],\n    {\n      on: transformOn,\n      bind: transformBind,\n      model: transformModel,\n    },\n  ];\n}\n\nexport function baseCompile(template: string | RootNode, options: CompilerOptions): CodegenResult {\n  // parse\n  const ast = isString(template) ? baseParse(template, options) : template;\n\n  // transform\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n  transform(ast, {\n    ...options,\n    nodeTransforms: [...nodeTransforms, ...(options.nodeTransforms || [])],\n    directiveTransforms: {\n      ...directiveTransforms,\n      ...(options.directiveTransforms || {}),\n    },\n  });\n\n  // codegen\n  const code = generate(ast, options);\n\n  return code;\n}\n"
  },
  {
    "path": "impl/compiler-core/src/index.ts",
    "content": "export { baseCompile } from \"./compile\";\nexport { baseParse } from \"./parse\";\n\nexport * from \"./ast\";\nexport * from \"./options\";\n\nexport * from \"./transform\";\nexport { transformOn } from \"./transforms/vOn\";\nexport { transformModel } from \"./transforms/vModel\";\nexport { transformBind } from \"./transforms/vBind\";\nexport { transformExpression } from \"./transforms/transformExpression\";\nexport { toValidAssetId } from \"./transforms/transformElement\";\nexport { processFor, createForLoopParams, type ForParseResult } from \"./transforms/vFor\";\nexport { processIf } from \"./transforms/vIf\";\nexport { ConstantTypes } from \"./transforms/hoistStatic\";\n\nexport * from \"./codegen\";\nexport * from \"./compile\";\n\nexport * from \"./utils\";\nexport * from \"./babelUtils\";\n\nexport { registerRuntimeHelpers } from \"./runtimeHelpers\";\n"
  },
  {
    "path": "impl/compiler-core/src/options.ts",
    "content": "import type { ElementNode } from \"./ast\";\nimport type { TextModes } from \"./parse\";\nimport type { DirectiveTransform, NodeTransform } from \"./transform\";\n\nexport interface ParserOptions {\n  isNativeTag?: (tag: string) => boolean;\n  delimiters?: [string, string];\n  decodeEntities?: (rawText: string, asAttr: boolean) => string;\n  getTextMode?: (node: ElementNode, parent: ElementNode | undefined) => TextModes;\n}\n\nexport interface TransformOptions extends SharedTransformCodegenOptions {\n  nodeTransforms?: NodeTransform[];\n  directiveTransforms?: Record<string, DirectiveTransform | undefined>;\n  inline?: boolean;\n  bindingMetadata?: BindingMetadata;\n  hoistStatic?: boolean;\n  prefixIdentifiers?: boolean;\n}\n\nexport type BindingMetadata = {\n  [key: string]: BindingTypes | undefined;\n} & {\n  __isScriptSetup?: boolean;\n};\n\ninterface SharedTransformCodegenOptions {\n  isBrowser?: boolean;\n}\n\nexport const enum BindingTypes {\n  DATA = \"data\",\n  PROPS = \"props\",\n  SETUP_CONST = \"setup-const\",\n  SETUP_MAYBE_REF = \"setup-maybe-ref\",\n  SETUP_REF = \"setup-ref\",\n  SETUP_REACTIVE_CONST = \"setup-reactive-const\",\n  SETUP_LET = \"setup-let\",\n  LITERAL_CONST = \"literal-const\",\n  OPTIONS = \"options\",\n}\n\nexport interface CodegenOptions extends SharedTransformCodegenOptions {\n  inline?: boolean;\n  scopeId?: string;\n  ssr?: boolean;\n}\n\nexport type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions;\n"
  },
  {
    "path": "impl/compiler-core/src/parse.ts",
    "content": "import { isArray } from \"@chibivue/shared\";\n\nimport {\n  type AttributeNode,\n  type CommentNode,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  type InterpolationNode,\n  NodeTypes,\n  type Position,\n  type RootNode,\n  type SourceLocation,\n  type TemplateChildNode,\n  type TextNode,\n  createRoot,\n} from \"./ast\";\nimport type { ParserOptions } from \"./options\";\nimport { advancePositionWithClone, advancePositionWithMutation } from \"./utils\";\n\ntype AttributeValue =\n  | {\n      content: string;\n      loc: SourceLocation;\n    }\n  | undefined;\n\n// The default decoder only provides escapes for characters reserved as part of\n// the template syntax, and is only used if the custom renderer did not provide\n// a platform-specific decoder.\nconst decodeRE = /&(gt|lt|amp|apos|quot);/g;\nconst decodeMap: Record<string, string> = {\n  gt: \">\",\n  lt: \"<\",\n  amp: \"&\",\n  apos: \"'\",\n  quot: '\"',\n};\n\nexport const defaultParserOptions: Required<ParserOptions> = {\n  isNativeTag: (tag) => true,\n  delimiters: [`{{`, `}}`],\n  decodeEntities: (rawText: string): string => rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),\n  getTextMode: () => TextModes.DATA,\n};\n\nexport const enum TextModes {\n  //          | Elements | Entities | End sign               | Inside of\n  DATA, //    | ✔        | ✔        | End tags of ancestors |\n  RCDATA, //  | ✘        | ✔        | End tag of the parent | <textarea>\n  RAWTEXT, // | ✘        | ✘        | End tag of the parent | <style>,<script>\n  CDATA,\n  ATTRIBUTE_VALUE,\n}\n\nexport interface ParserContext {\n  options: ParserOptions;\n  readonly originalSource: string;\n  source: string;\n  offset: number;\n  line: number;\n  column: number;\n  inVPre: boolean;\n}\n\nexport function baseParse(content: string, rawOptions: ParserOptions): RootNode {\n  const context = createParserContext(content, rawOptions);\n  return createRoot(parseChildren(context, TextModes.DATA, []));\n}\n\nfunction createParserContext(content: string, rawOptions: ParserOptions): ParserContext {\n  const options = { ...defaultParserOptions };\n  let key: keyof ParserOptions;\n  for (key in rawOptions) {\n    // @ts-expect-error\n    options[key] = rawOptions[key] === undefined ? defaultParserOptions[key] : rawOptions[key];\n  }\n\n  return {\n    options,\n    column: 1,\n    line: 1,\n    offset: 0,\n    originalSource: content,\n    source: content,\n    inVPre: false,\n  };\n}\n\nfunction parseChildren(\n  context: ParserContext,\n  mode: TextModes,\n  ancestors: ElementNode[],\n): TemplateChildNode[] {\n  const nodes: TemplateChildNode[] = [];\n\n  while (!isEnd(context, mode, ancestors)) {\n    const s = context.source;\n    let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined;\n    if (mode === TextModes.DATA || mode === TextModes.RCDATA) {\n      if (startsWith(s, context.options.delimiters![0])) {\n        // Skip mustache when in v-pre\n        if (!context.inVPre) {\n          node = parseInterpolation(context, mode);\n        }\n      } else if (mode === TextModes.DATA && s[0] === \"<\") {\n        if (s[1] === \"!\") {\n          // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state\n          if (startsWith(s, \"<!--\")) {\n            node = parseComment(context);\n          }\n        } else if (/[a-z]/i.test(s[1])) {\n          node = parseElement(context, ancestors);\n        }\n      }\n    }\n\n    if (!node) {\n      node = parseText(context, mode);\n    }\n\n    if (isArray(node)) {\n      for (let i = 0; i < node.length; i++) {\n        pushNode(nodes, node[i]);\n      }\n    } else {\n      pushNode(nodes, node);\n    }\n  }\n\n  return nodes;\n}\n\nfunction parseComment(context: ParserContext): CommentNode {\n  const start = getCursor(context);\n  let content: string;\n\n  // Regular comment.\n  const match = /--(\\!)?>/.exec(context.source);\n  if (!match) {\n    content = context.source.slice(4);\n    advanceBy(context, context.source.length);\n    throw new Error(\"EOF_IN_COMMENT\"); // TODO: error handling\n  } else {\n    if (match.index <= 3) {\n      throw new Error(\"ABRUPT_CLOSING_OF_EMPTY_COMMENT\"); // TODO: error handling\n    }\n    if (match[1]) {\n      throw new Error(\"INCORRECTLY_CLOSED_COMMENT\"); // TODO: error handling\n    }\n    content = context.source.slice(4, match.index);\n\n    const s = context.source.slice(0, match.index);\n    let prevIndex = 1,\n      nestedIndex = 0;\n    while ((nestedIndex = s.indexOf(\"<!--\", prevIndex)) !== -1) {\n      advanceBy(context, nestedIndex - prevIndex + 1);\n      if (nestedIndex + 4 < s.length) {\n        throw new Error(\"NESTED_COMMENT\"); // TODO: error handling\n      }\n      prevIndex = nestedIndex + 1;\n    }\n    advanceBy(context, match.index + match[0].length - prevIndex + 1);\n  }\n\n  return {\n    type: NodeTypes.COMMENT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {\n  if (node.type === NodeTypes.TEXT) {\n    const prev = last(nodes);\n    // Merge if both this and the previous node are text and those are\n    // consecutive. This happens for cases like \"a < b\".\n    if (prev && prev.type === NodeTypes.TEXT) {\n      prev.content += node.content;\n      return;\n    }\n  }\n\n  nodes.push(node);\n}\n\nfunction parseInterpolation(\n  context: ParserContext,\n  mode: TextModes,\n): InterpolationNode | undefined {\n  const [open, close] = context.options.delimiters!;\n  const closeIndex = context.source.indexOf(close, open.length);\n  if (closeIndex === -1) return undefined;\n\n  const start = getCursor(context);\n  advanceBy(context, open.length);\n  const innerStart = getCursor(context);\n  const innerEnd = getCursor(context);\n  const rawContentLength = closeIndex - open.length;\n  const rawContent = context.source.slice(0, rawContentLength);\n  const preTrimContent = parseTextData(context, rawContentLength, mode);\n  const content = preTrimContent.trim();\n  const startOffset = preTrimContent.indexOf(content);\n  if (startOffset > 0) {\n    advancePositionWithMutation(innerStart, rawContent, startOffset);\n  }\n  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);\n  advancePositionWithMutation(innerEnd, rawContent, endOffset);\n  advanceBy(context, close.length);\n\n  return {\n    type: NodeTypes.INTERPOLATION,\n    content: {\n      type: NodeTypes.SIMPLE_EXPRESSION,\n      isStatic: false,\n      content,\n      loc: getSelection(context, innerStart, innerEnd),\n    },\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseText(context: ParserContext, mode: TextModes): TextNode {\n  const endTokens = [\"<\", context.options.delimiters![0]];\n\n  let endIndex = context.source.length;\n  for (let i = 0; i < endTokens.length; i++) {\n    const index = context.source.indexOf(endTokens[i], 1);\n    if (index !== -1 && endIndex > index) {\n      endIndex = index;\n    }\n  }\n\n  const start = getCursor(context);\n  const content = parseTextData(context, endIndex, mode);\n\n  return {\n    type: NodeTypes.TEXT,\n    content,\n    loc: getSelection(context, start),\n  };\n}\n\nfunction parseElement(context: ParserContext, ancestors: ElementNode[]): ElementNode | undefined {\n  // Start tag.\n  const parent = last(ancestors);\n  const element = parseTag(context, TagType.Start);\n\n  // Check for v-pre\n  const isPreBoundary = element.props.some(\n    (p) => p.type === NodeTypes.DIRECTIVE && p.name === \"pre\",\n  );\n  if (isPreBoundary) {\n    context.inVPre = true;\n  }\n\n  if (element.isSelfClosing) {\n    if (isPreBoundary) {\n      context.inVPre = false;\n    }\n    return element;\n  }\n\n  // Children.\n  ancestors.push(element);\n  const mode = context.options.getTextMode!(element, parent);\n  const children = parseChildren(context, mode, ancestors);\n  ancestors.pop();\n\n  element.children = children;\n\n  // End tag.\n  if (startsWithEndTagOpen(context.source, element.tag)) {\n    parseTag(context, TagType.End);\n  }\n\n  // End of v-pre\n  if (isPreBoundary) {\n    context.inVPre = false;\n  }\n\n  return element;\n}\n\nconst enum TagType {\n  Start,\n  End,\n}\n\nfunction parseTag(context: ParserContext, type: TagType): ElementNode {\n  // Tag open.\n  const start = getCursor(context);\n  const match = /^<\\/?([a-z][^\\t\\r\\n\\f />]*)/i.exec(context.source)!;\n  const tag = match[1];\n\n  advanceBy(context, match[0].length);\n  advanceSpaces(context);\n\n  // Attributes.\n  let props = parseAttributes(context, type);\n\n  // Tag close.\n  let isSelfClosing = false;\n\n  isSelfClosing = startsWith(context.source, \"/>\");\n  advanceBy(context, isSelfClosing ? 2 : 1);\n\n  let tagType = ElementTypes.ELEMENT;\n\n  if (tag === \"template\") {\n    tagType = ElementTypes.TEMPLATE;\n  } else if (isComponent(tag, props, context)) {\n    tagType = ElementTypes.COMPONENT;\n  }\n\n  return {\n    type: NodeTypes.ELEMENT,\n    tag,\n    tagType,\n    props,\n    children: [],\n    isSelfClosing,\n    codegenNode: undefined, // to be created during transform phase\n    loc: getSelection(context, start),\n  };\n}\n\nfunction isComponent(\n  tag: string,\n  props: (AttributeNode | DirectiveNode)[],\n  context: ParserContext,\n) {\n  const options = context.options;\n  return options.isNativeTag && !options.isNativeTag(tag);\n}\n\nfunction parseAttributes(context: ParserContext, type: TagType): (AttributeNode | DirectiveNode)[] {\n  const props = [];\n  const attributeNames = new Set<string>();\n  while (\n    context.source.length > 0 &&\n    !startsWith(context.source, \">\") &&\n    !startsWith(context.source, \"/>\")\n  ) {\n    const attr = parseAttribute(context, attributeNames);\n\n    // Trim whitespace between class\n    // https://github.com/vuejs/core/issues/4251\n    if (attr.type === NodeTypes.ATTRIBUTE && attr.value && attr.name === \"class\") {\n      attr.value.content = attr.value.content.replace(/\\s+/g, \" \").trim();\n    }\n\n    if (type === TagType.Start) {\n      props.push(attr);\n    }\n\n    advanceSpaces(context);\n  }\n  return props;\n}\n\nfunction parseAttribute(\n  context: ParserContext,\n  nameSet: Set<string>,\n): AttributeNode | DirectiveNode {\n  // Name.\n  const start = getCursor(context);\n  const match = /^[^\\t\\r\\n\\f />][^\\t\\r\\n\\f />=]*/.exec(context.source)!;\n  const name = match[0];\n\n  nameSet.add(name);\n\n  advanceBy(context, name.length);\n\n  // Value\n  let value: AttributeValue = undefined;\n\n  if (/^[\\t\\r\\n\\f ]*=/.test(context.source)) {\n    advanceSpaces(context);\n    advanceBy(context, 1);\n    advanceSpaces(context);\n    value = parseAttributeValue(context);\n  }\n\n  // directive\n  const loc = getSelection(context, start);\n\n  // Don't parse as directive when in v-pre (except for v-pre itself)\n  if (context.inVPre && name !== \"v-pre\") {\n    return {\n      type: NodeTypes.ATTRIBUTE,\n      name,\n      value: value && {\n        type: NodeTypes.TEXT,\n        content: value.content,\n        loc: value.loc,\n      },\n      loc,\n    };\n  }\n\n  if (/^(v-[A-Za-z0-9-]|:|\\.|@|#)/.test(name)) {\n    const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^\\.|^@|^#)(\\[[^\\]]+\\]|[^\\.]+))?(.+)?$/i.exec(name)!;\n\n    let isPropShorthand = startsWith(name, \".\");\n\n    let dirName =\n      match[1] ||\n      (isPropShorthand || startsWith(name, \":\") ? \"bind\" : startsWith(name, \"@\") ? \"on\" : \"\");\n\n    let arg: ExpressionNode | undefined;\n\n    if (match[2]) {\n      const startOffset = name.lastIndexOf(match[2]);\n      const loc = getSelection(\n        context,\n        getNewPosition(context, start, startOffset),\n        getNewPosition(context, start, startOffset + match[2].length),\n      );\n      let content = match[2];\n      let isStatic = true;\n\n      if (content.startsWith(\"[\")) {\n        isStatic = false;\n        if (!content.endsWith(\"]\")) {\n          console.error(`Invalid dynamic argument expression: ${content}`);\n          content = content.slice(1);\n        } else {\n          content = content.slice(1, content.length - 1);\n        }\n      }\n\n      arg = {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content,\n        isStatic,\n        loc,\n      };\n    }\n\n    const modifiers = match[3] ? match[3].slice(1).split(\".\") : [];\n\n    return {\n      type: NodeTypes.DIRECTIVE,\n      name: dirName,\n      exp: value && {\n        type: NodeTypes.SIMPLE_EXPRESSION,\n        content: value.content,\n        isStatic: false,\n        loc: value.loc,\n      },\n      modifiers,\n      loc,\n      arg,\n    };\n  }\n\n  return {\n    type: NodeTypes.ATTRIBUTE,\n    name,\n    value: value && {\n      type: NodeTypes.TEXT,\n      content: value.content,\n      loc: value.loc,\n    },\n    loc,\n  };\n}\n\nfunction parseAttributeValue(context: ParserContext): AttributeValue {\n  const start = getCursor(context);\n  let content: string;\n\n  const quote = context.source[0];\n  const isQuoted = quote === `\"` || quote === `'`;\n  if (isQuoted) {\n    // Quoted value.\n    advanceBy(context, 1);\n\n    const endIndex = context.source.indexOf(quote);\n    if (endIndex === -1) {\n      content = parseTextData(context, context.source.length, TextModes.ATTRIBUTE_VALUE);\n    } else {\n      content = parseTextData(context, endIndex, TextModes.ATTRIBUTE_VALUE);\n      advanceBy(context, 1);\n    }\n  } else {\n    // Unquoted\n    const match = /^[^\\t\\r\\n\\f >]+/.exec(context.source);\n    if (!match) {\n      return undefined;\n    }\n    content = parseTextData(context, match[0].length, TextModes.ATTRIBUTE_VALUE);\n  }\n\n  return { content, loc: getSelection(context, start) };\n}\n\nfunction advanceBy(context: ParserContext, numberOfCharacters: number): void {\n  const { source } = context;\n  advancePositionWithMutation(context, source, numberOfCharacters);\n  context.source = source.slice(numberOfCharacters);\n}\n\nfunction isEnd(context: ParserContext, mode: TextModes, ancestors: ElementNode[]): boolean {\n  const s = context.source;\n\n  switch (mode) {\n    case TextModes.DATA:\n      if (startsWith(s, \"</\")) {\n        // TODO: probably bad performance\n        for (let i = ancestors.length - 1; i >= 0; --i) {\n          if (startsWithEndTagOpen(s, ancestors[i].tag)) {\n            return true;\n          }\n        }\n      }\n      break;\n\n    case TextModes.RCDATA:\n    case TextModes.RAWTEXT: {\n      const parent = last(ancestors);\n      if (parent && startsWithEndTagOpen(s, parent.tag)) {\n        return true;\n      }\n      break;\n    }\n\n    case TextModes.CDATA:\n      if (startsWith(s, \"]]>\")) {\n        return true;\n      }\n      break;\n  }\n\n  return !s;\n}\n\nfunction startsWith(source: string, searchString: string): boolean {\n  return source.startsWith(searchString);\n}\n\nfunction advanceSpaces(context: ParserContext): void {\n  const match = /^[\\t\\r\\n\\f ]+/.exec(context.source);\n  if (match) {\n    advanceBy(context, match[0].length);\n  }\n}\n\n/**\n * Get text data with a given length from the current location.\n * This translates HTML entities in the text data.\n */\nfunction parseTextData(context: ParserContext, length: number, mode: TextModes): string {\n  const rawText = context.source.slice(0, length);\n  advanceBy(context, length);\n  if (mode === TextModes.RAWTEXT || mode === TextModes.CDATA || !rawText.includes(\"&\")) {\n    return rawText;\n  } else {\n    // DATA or RCDATA containing \"&\"\". Entity decoding required.\n    return context.options.decodeEntities!(rawText, mode === TextModes.ATTRIBUTE_VALUE);\n  }\n}\n\nfunction getCursor(context: ParserContext): Position {\n  const { column, line, offset } = context;\n  return { column, line, offset };\n}\n\nfunction getSelection(context: ParserContext, start: Position, end?: Position): SourceLocation {\n  end = end || getCursor(context);\n  return {\n    start,\n    end,\n    source: context.originalSource.slice(start.offset, end.offset),\n  };\n}\n\nfunction getNewPosition(\n  context: ParserContext,\n  start: Position,\n  numberOfCharacters: number,\n): Position {\n  return advancePositionWithClone(\n    start,\n    context.originalSource.slice(start.offset, numberOfCharacters),\n    numberOfCharacters,\n  );\n}\n\nfunction last<T>(xs: T[]): T | undefined {\n  return xs[xs.length - 1];\n}\n\nfunction startsWithEndTagOpen(source: string, tag: string): boolean {\n  return (\n    startsWith(source, \"</\") &&\n    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&\n    /[\\t\\r\\n\\f />]/.test(source[2 + tag.length] || \">\")\n  );\n}\n"
  },
  {
    "path": "impl/compiler-core/src/runtimeHelpers.ts",
    "content": "export const FRAGMENT: unique symbol = Symbol();\nexport const CREATE_VNODE: unique symbol = Symbol();\nexport const CREATE_ELEMENT_VNODE: unique symbol = Symbol();\nexport const CREATE_COMMENT: unique symbol = Symbol();\nexport const RESOLVE_COMPONENT: unique symbol = Symbol(``);\nexport const WITH_DIRECTIVES: unique symbol = Symbol();\nexport const RENDER_LIST: unique symbol = Symbol();\nexport const TO_DISPLAY_STRING: unique symbol = Symbol();\nexport const MERGE_PROPS: unique symbol = Symbol();\nexport const NORMALIZE_CLASS: unique symbol = Symbol();\nexport const NORMALIZE_STYLE: unique symbol = Symbol();\nexport const NORMALIZE_PROPS: unique symbol = Symbol();\n\nexport const TO_HANDLERS: unique symbol = Symbol();\nexport const TO_HANDLER_KEY: unique symbol = Symbol();\nexport const UNREF: unique symbol = Symbol();\n\nexport const helperNameMap: Record<symbol, string> = {\n  [FRAGMENT]: `Fragment`,\n  [CREATE_VNODE]: `createVNode`,\n  [CREATE_ELEMENT_VNODE]: `createElementVNode`,\n  [CREATE_COMMENT]: `createCommentVNode`,\n  [RESOLVE_COMPONENT]: `resolveComponent`,\n  [TO_DISPLAY_STRING]: `toDisplayString`,\n  [MERGE_PROPS]: `mergeProps`,\n  [NORMALIZE_CLASS]: `normalizeClass`,\n  [NORMALIZE_STYLE]: `normalizeStyle`,\n  [NORMALIZE_PROPS]: `normalizeProps`,\n  [TO_HANDLERS]: \"toHandlers\",\n  [TO_HANDLER_KEY]: `toHandlerKey`,\n  [WITH_DIRECTIVES]: `withDirectives`,\n  [RENDER_LIST]: `renderList`,\n  [UNREF]: `unref`,\n};\n\nexport function registerRuntimeHelpers(helpers: Record<symbol, string>): void {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transform.ts",
    "content": "import { isArray, isString } from \"@chibivue/shared\";\nimport {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  NodeTypes,\n  type ParentNode,\n  type Property,\n  type RootNode,\n  type SimpleExpressionNode,\n  type TemplateChildNode,\n  createVNodeCall,\n  createSimpleExpression,\n} from \"./ast\";\nimport type { TransformOptions } from \"./options\";\nimport { CREATE_COMMENT, FRAGMENT, TO_DISPLAY_STRING, helperNameMap } from \"./runtimeHelpers\";\nimport { hoistStatic as hoistStaticTransform } from \"./transforms/hoistStatic\";\n\nexport type NodeTransform = (\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n) => void | (() => void) | (() => void)[];\n\nexport type DirectiveTransform = (\n  dir: DirectiveNode,\n  node: ElementNode,\n  context: TransformContext,\n  augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,\n) => DirectiveTransformResult;\n\nexport interface DirectiveTransformResult {\n  props: Property[];\n  needRuntime?: boolean | symbol;\n}\n\nexport type StructuralDirectiveTransform = (\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n) => void | (() => void);\n\nexport interface TransformContext extends Required<TransformOptions> {\n  helpers: Map<symbol, number>;\n  components: Set<string>;\n  hoists: (TemplateChildNode | ExpressionNode)[];\n  currentNode: RootNode | TemplateChildNode | null;\n  identifiers: { [name: string]: number | undefined };\n  scopes: {\n    vFor: number;\n  };\n  parent: ParentNode | null;\n  childIndex: number;\n  inline: boolean;\n  helper<T extends symbol>(name: T): T;\n  helperString(name: symbol): string;\n  replaceNode(node: TemplateChildNode): void;\n  removeNode(node?: TemplateChildNode): void;\n  onNodeRemoved(): void;\n  addIdentifiers(exp: ExpressionNode | string): void;\n  removeIdentifiers(exp: ExpressionNode | string): void;\n  hoist(exp: TemplateChildNode | ExpressionNode): ExpressionNode;\n}\n\nexport function createTransformContext(\n  root: RootNode,\n  {\n    nodeTransforms = [],\n    directiveTransforms = {},\n    inline = false,\n    bindingMetadata = Object.create(null),\n    isBrowser = false,\n    hoistStatic = false,\n    prefixIdentifiers = false,\n  }: TransformOptions,\n): TransformContext {\n  const context: TransformContext = {\n    isBrowser,\n    nodeTransforms,\n    directiveTransforms,\n    helpers: new Map(),\n    components: new Set(),\n    hoists: [],\n    currentNode: root,\n    identifiers: Object.create(null),\n    scopes: {\n      vFor: 0,\n    },\n    parent: null,\n    childIndex: 0,\n    bindingMetadata,\n    inline,\n    hoistStatic,\n    prefixIdentifiers,\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    replaceNode(node) {\n      context.parent!.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      const list = context.parent!.children;\n      const removalIndex = node\n        ? list.indexOf(node)\n        : context.currentNode\n          ? context.childIndex\n          : -1;\n      if (!node || node === context.currentNode) {\n        // current node removed\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        // sibling node removed\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent!.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: () => {},\n    addIdentifiers(exp) {\n      if (isString(exp)) {\n        addId(exp);\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(addId);\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        addId(exp.content);\n      }\n    },\n    removeIdentifiers(exp) {\n      if (isString(exp)) {\n        removeId(exp);\n      } else if (exp.identifiers) {\n        exp.identifiers.forEach(removeId);\n      } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {\n        removeId(exp.content);\n      }\n    },\n    hoist(exp) {\n      context.hoists.push(exp);\n      const identifier = createSimpleExpression(`_hoisted_${context.hoists.length}`, false);\n      return identifier;\n    },\n  };\n\n  function addId(id: string) {\n    const { identifiers } = context;\n    if (identifiers[id] === undefined) {\n      identifiers[id] = 0;\n    }\n    identifiers[id]!++;\n  }\n\n  function removeId(id: string) {\n    context.identifiers[id]!--;\n  }\n\n  return context;\n}\n\nexport function transform(root: RootNode, options: TransformOptions): void {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  if (options.hoistStatic) {\n    hoistStaticTransform(root, context);\n  }\n  createRootCodegen(root, context);\n  root.components = [...context.components];\n  root.helpers = new Set([...context.helpers.keys()]);\n  root.hoists = context.hoists;\n}\n\nfunction createRootCodegen(root: RootNode, context: TransformContext) {\n  const { helper } = context;\n  root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children);\n}\n\nexport function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext): void {\n  context.currentNode = node;\n  // apply transform plugins\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i = 0; i < nodeTransforms.length; i++) {\n    const onExit = nodeTransforms[i](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      // node was removed\n      return;\n    } else {\n      // node may have been replaced\n      node = context.currentNode;\n    }\n  }\n\n  switch (node.type) {\n    case NodeTypes.COMMENT:\n      context.helper(CREATE_COMMENT);\n      break;\n    case NodeTypes.INTERPOLATION: // no need to traverse, but we need to inject toString helper\n      context.helper(TO_DISPLAY_STRING);\n      break;\n\n    case NodeTypes.IF:\n      for (let i = 0; i < node.branches.length; i++) {\n        traverseNode(node.branches[i], context);\n      }\n      break;\n    case NodeTypes.IF_BRANCH:\n    case NodeTypes.ELEMENT:\n    case NodeTypes.ROOT:\n    case NodeTypes.FOR:\n      traverseChildren(node, context);\n      break;\n  }\n\n  // exit transforms\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\n\nexport function traverseChildren(parent: ParentNode, context: TransformContext): void {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\n\nexport function createStructuralDirectiveTransform(\n  name: string | RegExp,\n  fn: StructuralDirectiveTransform,\n): NodeTransform {\n  const matches = isString(name) ? (n: string) => n === name : (n: string) => name.test(n);\n\n  return (node, context) => {\n    if (node.type === NodeTypes.ELEMENT) {\n      const { props } = node;\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/hoistStatic.ts",
    "content": "import { NodeTypes } from \"../ast\";\nimport type {\n  CallExpression,\n  ElementNode,\n  PlainElementNode,\n  RootNode,\n  TemplateChildNode,\n  VNodeCall,\n} from \"../ast\";\nimport type { TransformContext } from \"../transform\";\n\nexport const enum ConstantTypes {\n  NOT_CONSTANT = 0,\n  CAN_SKIP_PATCH = 1,\n  CAN_HOIST = 2,\n  CAN_STRINGIFY = 3,\n}\n\nexport function hoistStatic(root: RootNode, context: TransformContext): void {\n  walk(root, context, new Map());\n}\n\nfunction walk(\n  node: RootNode | TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): void {\n  const { children } = node as RootNode | ElementNode;\n  if (!children) return;\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (\n      child.type === NodeTypes.ELEMENT &&\n      child.tagType === 0 // ElementTypes.ELEMENT\n    ) {\n      const constantType = getConstantType(child, context, resultCache);\n      if (constantType > ConstantTypes.NOT_CONSTANT) {\n        if (constantType >= ConstantTypes.CAN_HOIST) {\n          const codegenNode = child.codegenNode as VNodeCall | undefined;\n          if (codegenNode && codegenNode.type === NodeTypes.VNODE_CALL) {\n            codegenNode.isStatic = true;\n            context.hoists.push(codegenNode as unknown as TemplateChildNode);\n            child.codegenNode = context.hoist(\n              codegenNode as unknown as TemplateChildNode,\n            ) as unknown as VNodeCall;\n          }\n        }\n      } else {\n        walk(child, context, resultCache);\n      }\n    }\n  }\n}\n\nexport function getConstantType(\n  node: TemplateChildNode,\n  context: TransformContext,\n  resultCache: Map<TemplateChildNode, ConstantTypes>,\n): ConstantTypes {\n  const cached = resultCache.get(node);\n  if (cached !== undefined) {\n    return cached;\n  }\n\n  if (node.type === NodeTypes.ELEMENT) {\n    if (node.tagType !== 0) {\n      // not a plain element\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    const element = node as PlainElementNode;\n    const codegenNode = element.codegenNode;\n\n    if (!codegenNode || codegenNode.type !== NodeTypes.VNODE_CALL) {\n      resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n      return ConstantTypes.NOT_CONSTANT;\n    }\n\n    // Check if it has dynamic props\n    if (codegenNode.props) {\n      const propsType = codegenNode.props.type;\n      if (propsType !== NodeTypes.JS_OBJECT_EXPRESSION) {\n        resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n        return ConstantTypes.NOT_CONSTANT;\n      }\n\n      const properties = codegenNode.props.properties;\n      for (let i = 0; i < properties.length; i++) {\n        const { key, value } = properties[i];\n        if (key.type !== NodeTypes.SIMPLE_EXPRESSION || !key.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n        if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // Check children\n    if (element.children) {\n      for (let i = 0; i < element.children.length; i++) {\n        const child = element.children[i];\n        const childType = getConstantType(child, context, resultCache);\n        if (childType === ConstantTypes.NOT_CONSTANT) {\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    // Check directives\n    if (element.props && element.props.length > 0) {\n      for (const prop of element.props) {\n        if (prop.type === NodeTypes.DIRECTIVE) {\n          // Has directive, not constant\n          resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n          return ConstantTypes.NOT_CONSTANT;\n        }\n      }\n    }\n\n    resultCache.set(node, ConstantTypes.CAN_HOIST);\n    return ConstantTypes.CAN_HOIST;\n  }\n\n  if (node.type === NodeTypes.TEXT) {\n    resultCache.set(node, ConstantTypes.CAN_STRINGIFY);\n    return ConstantTypes.CAN_STRINGIFY;\n  }\n\n  if (node.type === NodeTypes.INTERPOLATION) {\n    resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n    return ConstantTypes.NOT_CONSTANT;\n  }\n\n  resultCache.set(node, ConstantTypes.NOT_CONSTANT);\n  return ConstantTypes.NOT_CONSTANT;\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/transformElement.ts",
    "content": "import { isSymbol } from \"@chibivue/shared\";\n\nimport {\n  type ArrayExpression,\n  type CallExpression,\n  type ComponentNode,\n  type DirectiveArguments,\n  type DirectiveNode,\n  type ElementNode,\n  ElementTypes,\n  type ExpressionNode,\n  NodeTypes,\n  type ObjectExpression,\n  type TemplateTextChildNode,\n  type VNodeCall,\n  createArrayExpression,\n  createCallExpression,\n  createObjectExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { BindingTypes } from \"../options\";\nimport {\n  MERGE_PROPS,\n  NORMALIZE_CLASS,\n  NORMALIZE_PROPS,\n  NORMALIZE_STYLE,\n  RESOLVE_COMPONENT,\n  TO_HANDLERS,\n} from \"../runtimeHelpers\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { isStaticExp } from \"../utils\";\n\nconst directiveImportMap = new WeakMap<DirectiveNode, symbol>();\n\nexport type PropsExpression = ObjectExpression | CallExpression | ExpressionNode;\n\nexport const transformElement: NodeTransform = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode!;\n\n    if (\n      !(\n        node.type === NodeTypes.ELEMENT &&\n        (node.tagType === ElementTypes.ELEMENT || node.tagType === ElementTypes.COMPONENT)\n      )\n    ) {\n      return;\n    }\n\n    const { tag, props } = node;\n    const isComponent = node.tagType === ElementTypes.COMPONENT;\n\n    const vnodeTag = isComponent\n      ? resolveComponentType(node as ComponentNode, context)\n      : `\"${tag}\"`;\n    let vnodeProps: VNodeCall[\"props\"];\n    let vnodeDirectives: VNodeCall[\"directives\"];\n    let vnodeChildren: VNodeCall[\"children\"];\n\n    // props\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(node, context);\n      vnodeProps = propsBuildResult.props;\n\n      const directives = propsBuildResult.directives;\n      vnodeDirectives = directives.length\n        ? (createArrayExpression(\n            directives.map((dir) => buildDirectiveArgs(dir, context)),\n          ) as DirectiveArguments)\n        : undefined;\n    }\n\n    // children\n    if (node.children.length > 0) {\n      if (node.children.length === 1) {\n        const child = node.children[0];\n        const type = child.type;\n        // check for dynamic text children\n        const hasDynamicTextChild = type === NodeTypes.INTERPOLATION;\n\n        // pass directly if the only child is a text node\n        // (plain / interpolation / expression)\n        if (hasDynamicTextChild || type === NodeTypes.TEXT) {\n          vnodeChildren = child as TemplateTextChildNode;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n\n    node.codegenNode = createVNodeCall(\n      context,\n      vnodeTag,\n      vnodeProps,\n      vnodeChildren,\n      vnodeDirectives,\n      isComponent,\n    );\n  };\n};\n\nexport function buildProps(\n  node: ElementNode,\n  context: TransformContext,\n): { props: PropsExpression | undefined; directives: DirectiveNode[] } {\n  const { props, loc: elementLoc } = node;\n  let properties: ObjectExpression[\"properties\"] = [];\n  const runtimeDirectives: DirectiveNode[] = [];\n  const mergeArgs: PropsExpression[] = [];\n\n  const pushMergeArg = (arg?: PropsExpression) => {\n    if (properties.length) {\n      mergeArgs.push(createObjectExpression(properties, elementLoc));\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      const { name, value } = prop;\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true),\n          createSimpleExpression(value ? value.content : \"\", true),\n        ),\n      );\n    } else {\n      // directives\n      const { name, arg, exp, loc } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n\n      // special case for v-bind and v-on with no argument\n      if (!arg && (isVBind || isVOn)) {\n        if (exp) {\n          if (isVBind) {\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            // v-on=\"obj\" -> toHandlers(obj)\n            pushMergeArg({\n              type: NodeTypes.JS_CALL_EXPRESSION,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: [exp],\n            });\n          }\n        }\n        continue;\n      }\n\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        // has built-in directive transform.\n        const { props, needRuntime } = directiveTransform(prop, node, context);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props, elementLoc));\n        } else {\n          properties.push(...props);\n        }\n        if (needRuntime) {\n          runtimeDirectives.push(prop);\n          if (isSymbol(needRuntime)) {\n            directiveImportMap.set(prop, needRuntime);\n          }\n        }\n      }\n    }\n  }\n\n  let propsExpression: PropsExpression | undefined = undefined;\n\n  // has v-bind=\"object\" or v-on=\"object\", wrap with mergeProps\n  if (mergeArgs.length) {\n    // close up any not-yet-merged props\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);\n    } else {\n      // single v-bind with nothing else - no need for a mergeProps call\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(properties);\n  }\n\n  if (propsExpression) {\n    switch (propsExpression.type) {\n      case NodeTypes.JS_OBJECT_EXPRESSION:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          }\n        }\n\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n\n        if (classProp && !isStaticExp(classProp.value)) {\n          classProp.value = createCallExpression(context.helper(NORMALIZE_CLASS), [\n            classProp.value,\n          ]);\n        }\n\n        if (\n          styleProp &&\n          ((styleProp.value.type === NodeTypes.SIMPLE_EXPRESSION &&\n            styleProp.value.content.trim()[0] === `[`) ||\n            styleProp.value.type === NodeTypes.JS_ARRAY_EXPRESSION)\n        ) {\n          styleProp.value = createCallExpression(context.helper(NORMALIZE_STYLE), [\n            styleProp.value,\n          ]);\n        } else {\n          // dynamic key binding, wrap with `normalizeProps`\n          propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [\n            propsExpression,\n          ]);\n        }\n        break;\n\n      case NodeTypes.JS_CALL_EXPRESSION:\n        // mergeProps call, do nothing\n        break;\n\n      default:\n        // single v-bind\n        propsExpression = createCallExpression(context.helper(NORMALIZE_PROPS), [propsExpression]);\n        break;\n    }\n  }\n\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n  };\n}\n\nexport function buildDirectiveArgs(dir: DirectiveNode, context: TransformContext): ArrayExpression {\n  const dirArgs: ArrayExpression[\"elements\"] = [];\n  const runtime = directiveImportMap.get(dir);\n  if (runtime) {\n    dirArgs.push(context.helperString(runtime));\n  }\n  if (dir.exp) dirArgs.push(dir.exp);\n  if (dir.arg) {\n    if (!dir.exp) {\n      dirArgs.push(`void 0`);\n    }\n    dirArgs.push(dir.arg);\n  }\n  return createArrayExpression(dirArgs, dir.loc);\n}\n\nexport function resolveComponentType(node: ComponentNode, context: TransformContext): string {\n  let { tag } = node;\n\n  // TODO: 1. dynamic component\n\n  // TODO: 1.5 v-is (TODO: Deprecate)\n\n  // TODO: 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)\n\n  // 3. user component (from setup bindings)\n  const fromSetup = resolveSetupReference(tag, context);\n  if (fromSetup) {\n    return fromSetup;\n  }\n\n  // TODO: 4. Self referencing component (inferred from filename)\n  context.helper(RESOLVE_COMPONENT);\n\n  // 5. user component (resolve)\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\n\n// Built-in components that should NOT be resolved from setup bindings\n// These need special runtime handling\nconst BUILTIN_COMPONENTS = new Set([\n  \"Transition\",\n  \"TransitionGroup\",\n  \"KeepAlive\",\n  \"Teleport\",\n  \"Suspense\",\n]);\n\nfunction resolveSetupReference(name: string, context: TransformContext): string | undefined {\n  // Don't resolve built-in components from setup bindings\n  if (BUILTIN_COMPONENTS.has(name)) {\n    return undefined;\n  }\n\n  const bindings = context.bindingMetadata;\n  if (!bindings) {\n    return undefined;\n  }\n\n  const bindingType = bindings[name];\n  if (\n    bindingType === BindingTypes.SETUP_CONST ||\n    bindingType === BindingTypes.SETUP_REACTIVE_CONST\n  ) {\n    return name;\n  }\n\n  // Also check PascalCase version of kebab-case name\n  const pascalName = name\n    .split(\"-\")\n    .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n    .join(\"\");\n  if (pascalName !== name) {\n    const pascalBindingType = bindings[pascalName];\n    if (\n      pascalBindingType === BindingTypes.SETUP_CONST ||\n      pascalBindingType === BindingTypes.SETUP_REACTIVE_CONST\n    ) {\n      return pascalName;\n    }\n  }\n\n  return undefined;\n}\n\nexport function toValidAssetId(name: string, type: \"component\" | \"directive\" | \"filter\"): string {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/transformExpression.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport type { Identifier, Node } from \"@babel/types\";\nimport { genPropsAccessExp, hasOwn, makeMap } from \"@chibivue/shared\";\n\nimport {\n  type CompoundExpressionNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createSimpleExpression,\n} from \"../ast\";\nimport { walkIdentifiers } from \"../babelUtils\";\nimport type { NodeTransform, TransformContext } from \"../transform\";\nimport { advancePositionWithClone, isSimpleIdentifier } from \"../utils\";\nimport { BindingTypes } from \"../options\";\nimport { UNREF } from \"../runtimeHelpers\";\n\nconst isLiteralWhitelisted = makeMap(\"true,false,null,this\");\n\nexport const transformExpression: NodeTransform = (node, ctx) => {\n  if (node.type === NodeTypes.INTERPOLATION) {\n    node.content = processExpression(node.content as SimpleExpressionNode, ctx);\n  } else if (node.type === NodeTypes.ELEMENT) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === NodeTypes.DIRECTIVE && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(exp, ctx);\n        }\n        if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {\n          dir.arg = processExpression(arg, ctx);\n        }\n      }\n    }\n  }\n};\n\ninterface PrefixMeta {\n  start: number;\n  end: number;\n}\n\nexport function processExpression(\n  node: SimpleExpressionNode,\n  ctx: TransformContext,\n  asParams = false,\n): ExpressionNode {\n  if (ctx.isBrowser) {\n    return node;\n  }\n\n  const rawExp = node.content;\n\n  const { inline, bindingMetadata } = ctx;\n\n  const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => {\n    const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw];\n    // x = y\n    const isAssignmentLVal = parent && parent.type === \"AssignmentExpression\" && parent.left === id;\n    // x++\n    const isUpdateArg = parent && parent.type === \"UpdateExpression\" && parent.argument === id;\n\n    if (inline) {\n      if (type === BindingTypes.SETUP_CONST) {\n        return raw;\n      } else if (type === BindingTypes.SETUP_REACTIVE_CONST) {\n        return raw;\n      } else if (type === BindingTypes.SETUP_REF) {\n        return `${raw}.value`;\n      } else if (type === BindingTypes.SETUP_MAYBE_REF) {\n        return isAssignmentLVal || isUpdateArg\n          ? `${raw}.value`\n          : `${ctx.helperString(UNREF)}(${raw})`;\n      } else if (type === BindingTypes.PROPS) {\n        return genPropsAccessExp(raw);\n      }\n    }\n\n    return `_ctx.${raw}`;\n  };\n\n  if (isSimpleIdentifier(rawExp)) {\n    const isLiteral = isLiteralWhitelisted(rawExp);\n    const isScopeVarReference = ctx.identifiers[rawExp];\n    if (!asParams && !isScopeVarReference && !isLiteral) {\n      node.content = rewriteIdentifier(rawExp);\n    }\n    return node;\n  }\n\n  const source = `(${rawExp})${asParams ? `=>{}` : ``}`;\n  const ast = parse(source).program;\n  type QualifiedId = Identifier & PrefixMeta;\n  const ids: QualifiedId[] = [];\n  const parentStack: Node[] = [];\n  const knownIds: Record<string, number> = Object.create(ctx.identifiers);\n\n  walkIdentifiers(\n    ast,\n    (node) => {\n      node.name = rewriteIdentifier(node.name);\n      ids.push(node as QualifiedId);\n    },\n    knownIds,\n    parentStack,\n  );\n\n  const children: CompoundExpressionNode[\"children\"] = [];\n  ids.sort((a, b) => a.start - b.start);\n  ids.forEach((id, i) => {\n    const start = id.start - 1;\n    const end = id.end - 1;\n    const last = ids[i - 1];\n    const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);\n    if (leadingText.length) {\n      children.push(leadingText);\n    }\n\n    const source = rawExp.slice(start, end);\n    children.push(\n      createSimpleExpression(id.name, false, {\n        source,\n        start: advancePositionWithClone(node.loc.start, source, start),\n        end: advancePositionWithClone(node.loc.start, source, end),\n      }),\n    );\n    if (i === ids.length - 1 && end < rawExp.length) {\n      children.push(rawExp.slice(end));\n    }\n  });\n\n  let ret;\n  if (children.length) {\n    ret = createCompoundExpression(children, node.loc);\n  } else {\n    ret = node;\n  }\n\n  ret.identifiers = Object.keys(knownIds);\n\n  return ret;\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/vBind.ts",
    "content": "import { NodeTypes, createObjectProperty } from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\n// *with* args.\nexport const transformBind: DirectiveTransform = (dir, _node, _context) => {\n  const { exp } = dir;\n  const arg = dir.arg!;\n\n  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n\n  return { props: [createObjectProperty(arg, exp!)] };\n};\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/vFor.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type ExpressionNode,\n  type ForCodegenNode,\n  type ForIteratorExpression,\n  type ForNode,\n  type ForRenderListExpression,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type SourceLocation,\n  type VNodeCall,\n  createCallExpression,\n  createFunctionExpression,\n  createSimpleExpression,\n  createVNodeCall,\n} from \"../ast\";\nimport { FRAGMENT, RENDER_LIST } from \"../runtimeHelpers\";\nimport {\n  type NodeTransform,\n  type TransformContext,\n  createStructuralDirectiveTransform,\n} from \"../transform\";\nimport { getInnerRange } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformFor: NodeTransform = createStructuralDirectiveTransform(\n  \"for\",\n  (node, dir, context) => {\n    return processFor(node, dir, context, (forNode) => {\n      const renderExp = createCallExpression(context.helper(RENDER_LIST), [\n        forNode.source,\n      ]) as ForRenderListExpression;\n\n      forNode.codegenNode = createVNodeCall(\n        context,\n        context.helper(FRAGMENT),\n        undefined,\n        renderExp,\n      ) as ForCodegenNode;\n\n      return () => {\n        // finish the codegen now that all children have been traversed\n        const { children } = forNode;\n        const childBlock = (children[0] as ElementNode).codegenNode as VNodeCall;\n\n        renderExp.arguments.push(\n          createFunctionExpression(\n            createForLoopParams(forNode.parseResult),\n            childBlock,\n            true /* force newline */,\n          ) as ForIteratorExpression,\n        );\n      };\n    });\n  },\n);\n\nexport function processFor(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (forNode: ForNode) => (() => void) | undefined,\n): (() => void) | undefined {\n  const parseResult = parseForExpression(dir.exp as SimpleExpressionNode, context);\n\n  const { addIdentifiers, removeIdentifiers, scopes } = context;\n\n  // TODO: error handling when parseResult is undefined\n  const { source, value, key, index } = parseResult!;\n\n  const forNode: ForNode = {\n    type: NodeTypes.FOR,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    parseResult: parseResult!,\n    children: [node],\n  };\n\n  context.replaceNode(forNode);\n\n  // bookkeeping\n  scopes.vFor++;\n  // scope management\n  // inject identifiers to context\n  value && addIdentifiers(value);\n  key && addIdentifiers(key);\n  index && addIdentifiers(index);\n\n  const onExit = processCodegen && processCodegen(forNode);\n\n  return () => {\n    scopes.vFor--;\n    value && removeIdentifiers(value);\n    key && removeIdentifiers(key);\n    index && removeIdentifiers(index);\n\n    if (onExit) onExit();\n  };\n}\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\n\nexport interface ForParseResult {\n  source: ExpressionNode;\n  value: ExpressionNode | undefined;\n  key: ExpressionNode | undefined;\n  index: ExpressionNode | undefined;\n}\n\nexport function parseForExpression(\n  input: SimpleExpressionNode,\n  context: TransformContext,\n): ForParseResult | undefined {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n\n  if (!inMatch) return;\n\n  const [, LHS, RHS] = inMatch;\n  const result: ForParseResult = {\n    source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: undefined,\n    key: undefined,\n    index: undefined,\n  };\n\n  result.source = processExpression(result.source as SimpleExpressionNode, context);\n\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  const trimmedOffset = LHS.indexOf(valueContent);\n\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset: number | undefined;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(loc, keyContent, keyOffset);\n    }\n\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          loc,\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length,\n          ),\n        );\n      }\n    }\n\n    if (valueContent) {\n      result.value = createAliasExpression(loc, valueContent, trimmedOffset);\n    }\n  }\n\n  return result;\n}\n\nfunction createAliasExpression(\n  range: SourceLocation,\n  content: string,\n  offset: number,\n): SimpleExpressionNode {\n  return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));\n}\n\nexport function createForLoopParams(\n  { value, key, index }: ForParseResult,\n  memoArgs: ExpressionNode[] = [],\n): ExpressionNode[] {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\n\nfunction createParamsList(args: (ExpressionNode | undefined)[]): ExpressionNode[] {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args\n    .slice(0, i + 1)\n    .map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false));\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/vIf.ts",
    "content": "import {\n  type DirectiveNode,\n  type ElementNode,\n  type IfBranchNode,\n  type IfConditionalExpression,\n  type IfNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  type VNodeCall,\n  createCallExpression,\n  createConditionalExpression,\n} from \"../ast\";\nimport { CREATE_COMMENT } from \"../runtimeHelpers\";\nimport {\n  type NodeTransform,\n  type TransformContext,\n  createStructuralDirectiveTransform,\n  traverseNode,\n} from \"../transform\";\nimport { processExpression } from \"./transformExpression\";\n\nexport const transformIf: NodeTransform = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            context,\n          ) as IfConditionalExpression;\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode!);\n          parentCondition.alternate = createCodegenNodeForBranch(branch, context);\n        }\n      };\n    });\n  },\n);\n\nexport function processIf(\n  node: ElementNode,\n  dir: DirectiveNode,\n  context: TransformContext,\n  processCodegen?: (\n    node: IfNode,\n    branch: IfBranchNode,\n    isRoot: boolean,\n  ) => (() => void) | undefined,\n): (() => void) | undefined {\n  if (!context.isBrowser && dir.exp) {\n    dir.exp = processExpression(dir.exp as SimpleExpressionNode, context);\n  }\n\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode: IfNode = {\n      type: NodeTypes.IF,\n      loc: node.loc,\n      branches: [branch],\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent!.children;\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === NodeTypes.COMMENT) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n\n      if (sibling && sibling.type === NodeTypes.IF) {\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      }\n      break;\n    }\n  }\n}\n\nfunction createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {\n  return {\n    type: NodeTypes.IF_BRANCH,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? undefined : dir.exp,\n    children: [node],\n  };\n}\n\nfunction createCodegenNodeForBranch(\n  branch: IfBranchNode,\n  context: TransformContext,\n): IfConditionalExpression | VNodeCall {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch),\n      createCallExpression(context.helper(CREATE_COMMENT), ['\"\"', \"true\"]),\n    ) as IfConditionalExpression;\n  } else {\n    return createChildrenCodegenNode(branch);\n  }\n}\n\nfunction createChildrenCodegenNode(branch: IfBranchNode): VNodeCall {\n  const { children } = branch;\n  const firstChild = children[0];\n  const vnodeCall = (firstChild as ElementNode).codegenNode as VNodeCall;\n  return vnodeCall;\n}\n\nfunction getParentCondition(node: IfConditionalExpression): IfConditionalExpression {\n  while (true) {\n    if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n      if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/vModel.ts",
    "content": "import { camelize } from \"@chibivue/shared\";\n\nimport {\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport type { DirectiveTransform } from \"../transform\";\n\nexport const transformModel: DirectiveTransform = (dir, node, context) => {\n  const { exp, arg } = dir;\n  if (!exp) {\n    // TODO: error handling\n    throw new Error(`v-model is missing expression.`);\n  }\n\n  const propName = arg ? arg : createSimpleExpression(\"modelValue\", true);\n  const eventName = arg\n    ? `onUpdate:${camelize((arg as SimpleExpressionNode).content)}`\n    : `onUpdate:modelValue`;\n\n  const assignmentExp = createCompoundExpression([`$event => ((`, exp, `) = $event)`]);\n\n  const props = [\n    // modelValue: foo\n    createObjectProperty(propName, dir.exp!),\n    // \"onUpdate:modelValue\": $event => (foo = $event)\n    createObjectProperty(eventName, assignmentExp),\n  ];\n\n  return { props };\n};\n"
  },
  {
    "path": "impl/compiler-core/src/transforms/vOn.ts",
    "content": "import {\n  type DirectiveNode,\n  type ExpressionNode,\n  NodeTypes,\n  type SimpleExpressionNode,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"../ast\";\nimport { TO_HANDLER_KEY } from \"../runtimeHelpers\";\nimport type { DirectiveTransform, DirectiveTransformResult } from \"../transform\";\nimport { isMemberExpression } from \"../utils\";\nimport { processExpression } from \"./transformExpression\";\n\nconst fnExpRE =\n  /^\\s*([\\w$_]+|(async\\s*)?\\([^)]*?\\))\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\n\nexport interface VOnDirectiveNode extends DirectiveNode {\n  arg: ExpressionNode;\n  exp: SimpleExpressionNode | undefined;\n}\n\nexport const transformOn: DirectiveTransform = (dir, _node, context, augmentor) => {\n  const { arg } = dir as VOnDirectiveNode;\n\n  let eventName: ExpressionNode;\n  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {\n    eventName = createCompoundExpression([`${context.helperString(TO_HANDLER_KEY)}(`, arg, `)`]);\n  } else {\n    // already a compound expression.\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n\n  // handler processing\n  let exp: ExpressionNode | undefined = dir.exp as SimpleExpressionNode | undefined;\n  if (exp && !exp.content?.trim()) {\n    exp = undefined;\n  }\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n\n    isInlineStatement && context.addIdentifiers(`$event`);\n    exp = dir.exp = processExpression(exp, context);\n    isInlineStatement && context.removeIdentifiers(`$event`);\n\n    if (isInlineStatement) {\n      // wrap inline statement in a function expression\n      exp = createCompoundExpression([\n        `${isInlineStatement ? \"$event\" : \"(...args)\"} => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`,\n      ]);\n    }\n  }\n\n  let ret: DirectiveTransformResult = {\n    props: [createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`))],\n  };\n\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n\n  return ret;\n};\n"
  },
  {
    "path": "impl/compiler-core/src/utils.ts",
    "content": "import {\n  type ElementNode,\n  type JSChildNode,\n  NodeTypes,\n  type Position,\n  type SimpleExpressionNode,\n  type SourceLocation,\n} from \"./ast\";\nimport { CREATE_ELEMENT_VNODE, CREATE_VNODE } from \"./runtimeHelpers\";\n\nexport const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>\n  p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic;\n\nconst nonIdentifierRE = /^\\d|[^\\$\\w]/;\nexport const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name);\n\nconst enum MemberExpLexState {\n  inMemberExp,\n  inBrackets,\n  inParens,\n  inString,\n}\n\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\n\n// FIXME: to more slim\nexport const isMemberExpression = (path: string): boolean => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = MemberExpLexState.inMemberExp;\n  let stateStack: MemberExpLexState[] = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType: \"'\" | '\"' | \"`\" | null = null;\n\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case MemberExpLexState.inMemberExp:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inBrackets;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inParens;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case MemberExpLexState.inBrackets:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inParens:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = MemberExpLexState.inString;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          // if the exp ends as a call then it should not be considered valid\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop()!;\n          }\n        }\n        break;\n      case MemberExpLexState.inString:\n        if (char === currentStringType) {\n          state = stateStack.pop()!;\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\n\nexport function advancePositionWithMutation(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  let linesCount = 0;\n  let lastNewLinePos = -1;\n  for (let i = 0; i < numberOfCharacters; i++) {\n    if (source.charCodeAt(i) === 10 /* newline char code */) {\n      linesCount++;\n      lastNewLinePos = i;\n    }\n  }\n\n  pos.offset += numberOfCharacters;\n  pos.line += linesCount;\n  pos.column =\n    lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos;\n\n  return pos;\n}\n\nexport function getInnerRange(loc: SourceLocation, offset: number, length: number): SourceLocation {\n  const source = loc.source.slice(offset, offset + length);\n  const newLoc: SourceLocation = {\n    source,\n    start: advancePositionWithClone(loc.start, loc.source, offset),\n    end: loc.end,\n  };\n\n  if (length != null) {\n    newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);\n  }\n\n  return newLoc;\n}\n\nexport function advancePositionWithClone(\n  pos: Position,\n  source: string,\n  numberOfCharacters: number = source.length,\n): Position {\n  return advancePositionWithMutation({ ...pos }, source, numberOfCharacters);\n}\n\nexport function findProp(\n  node: ElementNode,\n  name: string,\n  dynamicOnly: boolean = false,\n  allowEmpty: boolean = false,\n): ElementNode[\"props\"][0] | undefined {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      if (dynamicOnly) continue;\n      if (p.name === name && (p.value || allowEmpty)) {\n        return p;\n      }\n    } else if (p.name === \"bind\" && (p.exp || allowEmpty)) {\n      return p;\n    }\n  }\n}\n"
  },
  {
    "path": "impl/compiler-dom/package.json",
    "content": "{\n  \"name\": \"@chibivue/compiler-dom\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/compiler-dom/src/codegen.ts",
    "content": "import type { RootNode } from \"@chibivue/compiler-core\";\n\nexport interface CodegenResult {\n  code: string;\n  preamble: string;\n  ast: RootNode;\n}\n"
  },
  {
    "path": "impl/compiler-dom/src/index.ts",
    "content": "import type {\n  CompilerOptions,\n  DirectiveTransform,\n  ParserOptions,\n  RootNode,\n} from \"@chibivue/compiler-core\";\nimport { baseCompile, baseParse } from \"@chibivue/compiler-core\";\n\nexport { registerRuntimeHelpers } from \"@chibivue/compiler-core\";\nexport * from \"./runtimeHelpers\";\n\nimport type { CodegenResult } from \"./codegen\";\nimport { parserOptions } from \"./parserOptions\";\nexport { parserOptions } from \"./parserOptions\";\nimport { transformModel } from \"./transforms/vModel\";\nimport { transformOn } from \"./transforms/vOn\";\nimport { transformVText } from \"./transforms/vText\";\nimport { transformVHtml } from \"./transforms/vHtml\";\nimport { transformShow } from \"./transforms/vShow\";\n\nexport const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {\n  on: transformOn,\n  model: transformModel, // override compiler-core\n  text: transformVText,\n  html: transformVHtml,\n  show: transformShow,\n};\n\nexport function compile(template: string, options: CompilerOptions): CodegenResult {\n  return baseCompile(template, {\n    ...options,\n    ...parserOptions,\n    directiveTransforms: {\n      ...options.directiveTransforms,\n      ...DOMDirectiveTransforms,\n    },\n  }) as any;\n}\n\nexport function parse(template: string, options: ParserOptions = {}): RootNode {\n  return baseParse(template, { ...options, ...parserOptions });\n}\n"
  },
  {
    "path": "impl/compiler-dom/src/parserOptions.ts",
    "content": "import type { ParserOptions } from \"@chibivue/compiler-core\";\nimport { isHTMLTag } from \"@chibivue/shared\";\n\nexport const parserOptions: ParserOptions = {\n  isNativeTag: (tag) => isHTMLTag(tag),\n};\n"
  },
  {
    "path": "impl/compiler-dom/src/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"@chibivue/compiler-core\";\n\nexport const V_MODEL_TEXT: unique symbol = Symbol();\nexport const V_MODEL_DYNAMIC: unique symbol = Symbol();\nexport const V_SHOW: unique symbol = Symbol();\nexport const V_ON_WITH_MODIFIERS: unique symbol = Symbol();\nexport const V_ON_WITH_KEYS: unique symbol = Symbol();\n\nregisterRuntimeHelpers({\n  [V_MODEL_TEXT]: `vModelText`,\n  [V_MODEL_DYNAMIC]: `vModelDynamic`,\n  [V_SHOW]: `vShow`,\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n});\n"
  },
  {
    "path": "impl/compiler-dom/src/transforms/vHtml.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"@chibivue/compiler-core\";\n\nexport const transformVHtml: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-html is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-html will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "impl/compiler-dom/src/transforms/vModel.ts",
    "content": "import type { DirectiveTransform } from \"@chibivue/compiler-core\";\nimport {\n  ElementTypes,\n  NodeTypes,\n  transformModel as baseTransform,\n  findProp,\n} from \"@chibivue/compiler-core\";\n\nimport { V_MODEL_DYNAMIC, V_MODEL_TEXT } from \"../runtimeHelpers\";\n\nexport const transformModel: DirectiveTransform = (dir, node, context) => {\n  const baseResult = baseTransform(dir, node, context);\n\n  // base transform has errors OR component v-model (only need props)\n  if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {\n    return baseResult;\n  }\n\n  const { tag } = node;\n  if (tag === \"input\" || tag === \"textarea\") {\n    let directiveToUse = V_MODEL_TEXT;\n    if (tag === \"input\") {\n      const type = findProp(node, `type`);\n      if (type) {\n        if (type.type === NodeTypes.DIRECTIVE) {\n          directiveToUse = V_MODEL_DYNAMIC;\n        }\n      }\n    }\n    baseResult.needRuntime = context.helper(directiveToUse);\n  }\n\n  // native v-model doesn't need the `modelValue` props since they are also\n  baseResult.props = baseResult.props.filter(\n    (p) => !(p.key.type === NodeTypes.SIMPLE_EXPRESSION && p.key.content === \"modelValue\"),\n  );\n\n  return baseResult;\n};\n"
  },
  {
    "path": "impl/compiler-dom/src/transforms/vOn.ts",
    "content": "import type {\n  DirectiveTransform,\n  ExpressionNode,\n  SimpleExpressionNode,\n} from \"@chibivue/compiler-core\";\nimport {\n  NodeTypes,\n  transformOn as baseTransform,\n  createCallExpression,\n  createCompoundExpression,\n  createObjectProperty,\n  createSimpleExpression,\n  isStaticExp,\n} from \"@chibivue/compiler-core\";\nimport { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from \"../runtimeHelpers\";\nimport { capitalize, makeMap } from \"@chibivue/shared\";\n\nconst isEventOptionModifier = makeMap(`passive,once,capture`);\nconst isNonKeyModifier = makeMap(\n  // event propagation management\n  `stop,prevent,self,` +\n    // system modifiers + exact\n    `ctrl,shift,alt,meta,exact,` +\n    // mouse\n    `middle`,\n);\n\nconst maybeKeyModifier = makeMap(\"left,right\");\nconst isKeyboardEvent = makeMap(`onkeyup,onkeydown,onkeypress`, true);\n\nconst resolveModifiers = (key: ExpressionNode, modifiers: string[]) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent((key as SimpleExpressionNode).content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers,\n  };\n};\n\nconst transformClick = (key: ExpressionNode, event: string) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick\n    ? createSimpleExpression(event, true)\n    : key.type !== NodeTypes.SIMPLE_EXPRESSION\n      ? createCompoundExpression([`(`, key, `) === \"onClick\" ? \"${event}\" : (`, key, `)`])\n      : key;\n};\n\nexport const transformOn: DirectiveTransform = (dir, node, context) => {\n  return baseTransform(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(\n      key,\n      modifiers,\n    );\n\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers),\n      ]);\n    }\n\n    if (keyModifiers.length && (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers),\n      ]);\n    }\n\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key)\n        ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)\n        : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n\n    return {\n      props: [createObjectProperty(key, handlerExp)],\n    };\n  });\n};\n"
  },
  {
    "path": "impl/compiler-dom/src/transforms/vShow.ts",
    "content": "import type { DirectiveTransform } from \"@chibivue/compiler-core\";\nimport { V_SHOW } from \"../runtimeHelpers\";\n\nexport const transformShow: DirectiveTransform = (dir, _node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-show is missing expression.`, loc);\n  }\n\n  return {\n    props: [],\n    needRuntime: context.helper(V_SHOW),\n  };\n};\n"
  },
  {
    "path": "impl/compiler-dom/src/transforms/vText.ts",
    "content": "import {\n  type DirectiveTransform,\n  createObjectProperty,\n  createSimpleExpression,\n} from \"@chibivue/compiler-core\";\n\nexport const transformVText: DirectiveTransform = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    console.error(`v-text is missing expression.`);\n  }\n  if (node.children.length) {\n    console.error(`v-text will override element children.`);\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp || createSimpleExpression(\"\", true),\n      ),\n    ],\n  };\n};\n"
  },
  {
    "path": "impl/compiler-sfc/package.json",
    "content": "{\n  \"name\": \"@chibivue/compiler-sfc\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/compiler-sfc/src/compileScript.ts",
    "content": "import { parse as _parse } from \"@babel/parser\";\nimport type {\n  ArrayPattern,\n  CallExpression,\n  Declaration,\n  ExportSpecifier,\n  Identifier,\n  LVal,\n  Node,\n  ObjectExpression,\n  ObjectPattern,\n  Statement,\n  TSFunctionType,\n  TSTypeLiteral,\n} from \"@babel/types\";\nimport { walk } from \"estree-walker\";\nimport MagicString from \"magic-string\";\n\nimport type { BindingMetadata } from \"@chibivue/compiler-core\";\nimport { BindingTypes, getImportedName } from \"@chibivue/compiler-core\";\n\nimport type { SFCDescriptor, SFCScriptBlock } from \"./parse\";\nimport { compileTemplate } from \"./compileTemplate\";\n\nconst DEFINE_PROPS = \"defineProps\";\nconst DEFINE_EMITS = \"defineEmits\";\nconst DEFAULT_VAR = `__default__`;\n\nexport interface ImportBinding {\n  imported: string;\n  local: string;\n  source: string;\n  isFromSetup: boolean;\n}\n\nexport interface SFCScriptCompileOptions {\n  inlineTemplate?: boolean;\n}\n\nexport function compileScript(\n  sfc: SFCDescriptor,\n  options: SFCScriptCompileOptions,\n): SFCScriptBlock {\n  let { script, scriptSetup, source } = sfc;\n\n  const parserPlugins = [\"typescript\"] as const;\n\n  // prettier-ignore\n  const scriptAst = _parse(script?.content ?? \"\", { sourceType: \"module\", plugins: [...parserPlugins] }).program;\n  // prettier-ignore\n  const scriptSetupAst = _parse(scriptSetup?.content ?? \"\", { sourceType: \"module\", plugins: [...parserPlugins] }).program;\n\n  if (!scriptSetup) {\n    if (!script) {\n      throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`);\n    }\n\n    let content = script.content;\n    const bindings = analyzeScriptBindings(scriptAst.body);\n    return {\n      ...script,\n      content,\n      bindings,\n      scriptAst: scriptAst.body,\n      scriptSetupAst: scriptSetupAst.body,\n    };\n  }\n\n  // metadata that needs to be returned\n  const bindingMetadata: BindingMetadata = {};\n  const userImports: Record<string, ImportBinding> = Object.create(null);\n  const scriptBindings: Record<string, BindingTypes> = Object.create(null);\n  const setupBindings: Record<string, BindingTypes> = Object.create(null);\n\n  let defaultExport: Node | undefined;\n  let propsRuntimeDecl: Node | undefined;\n  let propsTypeDecl: TSTypeLiteral | undefined;\n  let propsIdentifier: string | undefined;\n  let emitsRuntimeDecl: Node | undefined;\n  let emitsTypeDecl: TSTypeLiteral | undefined;\n  let emitIdentifier: string | undefined;\n\n  // Props destructure support\n  interface PropsDestructureBinding {\n    local: string;\n    default?: string;\n  }\n  const propsDestructuredBindings: Record<string, PropsDestructureBinding> = Object.create(null);\n\n  const scriptStartOffset = script && script.loc.start.offset;\n  const scriptEndOffset = script && script.loc.end.offset;\n\n  const s = new MagicString(source);\n  const startOffset = scriptSetup.loc.start.offset;\n  const endOffset = scriptSetup.loc.end.offset;\n\n  function registerUserImport(\n    source: string,\n    local: string,\n    imported: string,\n    isFromSetup: boolean,\n  ) {\n    userImports[local] = {\n      imported,\n      local,\n      source,\n      isFromSetup,\n    };\n  }\n\n  function processDefineProps(node: Node, declId?: LVal): boolean {\n    if (!isCallOf(node, DEFINE_PROPS)) {\n      return false;\n    }\n\n    const callExpr = node as CallExpression;\n\n    // Check for type parameters: defineProps<{ foo: string }>()\n    if (callExpr.typeParameters && callExpr.typeParameters.params.length > 0) {\n      const typeArg = callExpr.typeParameters.params[0];\n      if (typeArg.type === \"TSTypeLiteral\") {\n        propsTypeDecl = typeArg;\n      }\n    } else {\n      // Runtime declaration\n      propsRuntimeDecl = node.arguments[0];\n    }\n\n    if (declId) {\n      if (declId.type === \"ObjectPattern\") {\n        // Props destructure: const { foo, bar = 'default' } = defineProps(...)\n        processPropsDestructure(declId);\n      } else {\n        propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!);\n      }\n    }\n    return true;\n  }\n\n  function processPropsDestructure(pattern: ObjectPattern) {\n    for (const prop of pattern.properties) {\n      if (prop.type === \"ObjectProperty\") {\n        const key = prop.key;\n        const value = prop.value;\n\n        // Get property name\n        let propKey: string;\n        if (key.type === \"Identifier\") {\n          propKey = key.name;\n        } else if (key.type === \"StringLiteral\") {\n          propKey = key.value;\n        } else {\n          continue;\n        }\n\n        // Get local variable name and default value\n        let local: string;\n        let defaultValue: string | undefined;\n\n        if (value.type === \"Identifier\") {\n          // const { count } = defineProps(...)\n          local = value.name;\n        } else if (value.type === \"AssignmentPattern\") {\n          // const { count = 0 } = defineProps(...)\n          if (value.left.type === \"Identifier\") {\n            local = value.left.name;\n            defaultValue = scriptSetup!.content.slice(value.right.start!, value.right.end!);\n          } else {\n            continue;\n          }\n        } else {\n          continue;\n        }\n\n        propsDestructuredBindings[propKey] = { local, default: defaultValue };\n      } else if (prop.type === \"RestElement\") {\n        // const { id, ...rest } = defineProps(...)\n        // Rest element needs special handling - for now, just skip\n        if (prop.argument.type === \"Identifier\") {\n          // We could support this later by creating a computed ref\n        }\n      }\n    }\n  }\n\n  function processDefineEmits(node: Node, declId?: LVal): boolean {\n    if (!isCallOf(node, DEFINE_EMITS)) {\n      return false;\n    }\n\n    const callExpr = node as CallExpression;\n\n    // Check for type parameters: defineEmits<{ (e: 'change'): void }>()\n    if (callExpr.typeParameters && callExpr.typeParameters.params.length > 0) {\n      const typeArg = callExpr.typeParameters.params[0];\n      if (typeArg.type === \"TSTypeLiteral\") {\n        emitsTypeDecl = typeArg;\n      }\n    } else {\n      // Runtime declaration\n      emitsRuntimeDecl = node.arguments[0];\n    }\n\n    if (declId) {\n      emitIdentifier =\n        declId.type === \"Identifier\"\n          ? declId.name\n          : scriptSetup!.content.slice(declId.start!, declId.end!);\n    }\n    return true;\n  }\n\n  function hoistNode(node: Statement) {\n    const start = node.start! + startOffset;\n    let end = node.end! + startOffset;\n    // locate comment\n    if (node.trailingComments && node.trailingComments.length > 0) {\n      const lastCommentNode = node.trailingComments[node.trailingComments.length - 1];\n      end = lastCommentNode.end! + startOffset;\n    }\n    // locate the end of whitespace between this statement and the next\n    while (end <= source.length) {\n      if (!/\\s/.test(source.charAt(end))) {\n        break;\n      }\n      end++;\n    }\n    s.move(start, end, 0);\n  }\n\n  // 1.1 walk import declaration of <script>\n  for (const node of scriptAst.body) {\n    if (node.type === \"ImportDeclaration\") {\n      if (node.type === \"ImportDeclaration\") {\n        for (const specifier of node.specifiers) {\n          const imported = getImportedName(specifier);\n          registerUserImport(node.source.value, specifier.local.name, imported, false);\n        }\n      }\n    }\n  }\n\n  // 1.2 walk import declarations of <script setup>\n  for (const node of scriptSetupAst.body) {\n    if (node.type === \"ImportDeclaration\") {\n      if (node.type === \"ImportDeclaration\") {\n        hoistNode(node);\n\n        let removed = 0;\n        const removeSpecifier = (i: number) => {\n          const removeLeft = i > removed;\n          removed++;\n          const current = node.specifiers[i];\n          const next = node.specifiers[i + 1];\n          s.remove(\n            removeLeft ? node.specifiers[i - 1].end! + startOffset : current.start! + startOffset,\n            next && !removeLeft ? next.start! + startOffset : current.end! + startOffset,\n          );\n        };\n\n        for (let i = 0; i < node.specifiers.length; i++) {\n          const specifier = node.specifiers[i];\n          const local = specifier.local.name;\n          const imported = getImportedName(specifier);\n          const source = node.source.value;\n          const existing = userImports[local];\n          if (existing) {\n            if (existing.source === source && existing.imported === imported) {\n              removeSpecifier(i);\n            }\n          } else {\n            registerUserImport(source, local, imported, true);\n          }\n        }\n        if (node.specifiers.length && removed === node.specifiers.length) {\n          s.remove(node.start! + startOffset, node.end! + startOffset);\n        }\n      }\n    }\n  }\n\n  // 1.3 resolve possible user import alias of `ref` and `reactive`\n  const vueImportAliases: Record<string, string> = {};\n  for (const key in userImports) {\n    const { source, imported, local } = userImports[key];\n    if ([\"chibivue\", \"chibivue-router\", \"chibivue-store\"].includes(source))\n      vueImportAliases[imported] = local;\n  }\n\n  // 2.1 process normal <script> body\n  if (script && scriptAst) {\n    for (const node of scriptAst.body) {\n      if (node.type === \"ExportDefaultDeclaration\") {\n        defaultExport = node;\n      } else if (node.type === \"ExportNamedDeclaration\") {\n        const defaultSpecifier = node.specifiers.find(\n          (s) => s.exported.type === \"Identifier\" && s.exported.name === \"default\",\n        ) as ExportSpecifier;\n        if (defaultSpecifier) {\n          defaultExport = node;\n          // 1. remove specifier\n          if (node.specifiers.length > 1) {\n            s.remove(\n              defaultSpecifier.start! + scriptStartOffset!,\n              defaultSpecifier.end! + scriptStartOffset!,\n            );\n          } else {\n            s.remove(node.start! + scriptStartOffset!, node.end! + scriptStartOffset!);\n          }\n          if (node.source) {\n            // export { x as default } from './x'\n            // rewrite to `import { x as __default__ } from './x'` and\n            // add to top\n            s.prepend(\n              `import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\\n`,\n            );\n          } else {\n            // export { x as default }\n            // rewrite to `const __default__ = x` and move to end\n            s.appendLeft(\n              scriptEndOffset!,\n              `\\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\\n`,\n            );\n          }\n        }\n        if (node.declaration) {\n          walkDeclaration(node.declaration, scriptBindings, vueImportAliases);\n        }\n      } else if (\n        (node.type === \"VariableDeclaration\" ||\n          node.type === \"FunctionDeclaration\" ||\n          node.type === \"ClassDeclaration\" ||\n          node.type === \"TSEnumDeclaration\") &&\n        !node.declare\n      ) {\n        walkDeclaration(node, scriptBindings, vueImportAliases);\n      }\n    }\n  }\n\n  // 2.2 process <script setup> body\n  for (const node of scriptSetupAst.body) {\n    if (\n      (node.type === \"VariableDeclaration\" ||\n        node.type === \"FunctionDeclaration\" ||\n        node.type === \"ClassDeclaration\") &&\n      !node.declare\n    ) {\n      walkDeclaration(node, setupBindings, vueImportAliases);\n    }\n\n    if (node.type === \"ExpressionStatement\") {\n      const expr = node.expression;\n      if (processDefineProps(expr) || processDefineEmits(expr)) {\n        s.remove(node.start! + startOffset, node.end! + startOffset);\n      }\n    }\n\n    if (node.type === \"VariableDeclaration\" && !node.declare) {\n      const total = node.declarations.length;\n      let left = total;\n      let lastNonRemoved: number | undefined;\n      for (let i = 0; i < total; i++) {\n        const decl = node.declarations[i];\n        const init = decl.init;\n        if (init) {\n          const declId = decl.id.type === \"VoidPattern\" ? undefined : decl.id;\n          const isDefineProps = processDefineProps(init, declId);\n          const isDefineEmits = processDefineEmits(init, declId);\n          if (isDefineProps || isDefineEmits) {\n            if (left === 1) {\n              s.remove(node.start! + startOffset, node.end! + startOffset);\n            } else {\n              let start = decl.start! + startOffset;\n              let end = decl.end! + startOffset;\n              if (i === total - 1) {\n                start = node.declarations[lastNonRemoved!].end! + startOffset;\n              } else {\n                end = node.declarations[i + 1].start! + startOffset;\n              }\n              s.remove(start, end);\n              left--;\n            }\n          } else {\n            lastNonRemoved = i;\n          }\n        }\n      }\n    }\n  }\n\n  // 6. remove non-script content\n  if (script) {\n    if (startOffset < scriptStartOffset!) {\n      // <script setup> before <script>\n      s.remove(0, startOffset);\n      s.remove(endOffset, scriptStartOffset!);\n      s.remove(scriptEndOffset!, source.length);\n    } else {\n      // <script> before <script setup>\n      s.remove(0, scriptStartOffset!);\n      s.remove(scriptEndOffset!, startOffset);\n      s.remove(endOffset, source.length);\n    }\n  } else {\n    // only <script setup>\n    s.remove(0, startOffset);\n    s.remove(endOffset, source.length);\n  }\n\n  // 7. analyze binding metadata\n  if (scriptAst) {\n    Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body));\n  }\n  if (propsRuntimeDecl) {\n    for (const key of getObjectExpressionKeys(propsRuntimeDecl as ObjectExpression)) {\n      bindingMetadata[key] = BindingTypes.PROPS;\n    }\n  }\n  // Register props from type declaration\n  if (propsTypeDecl) {\n    for (const key of extractPropsKeysFromType(propsTypeDecl)) {\n      bindingMetadata[key] = BindingTypes.PROPS;\n    }\n  }\n  // Register destructured props as PROPS bindings\n  for (const key in propsDestructuredBindings) {\n    const { local } = propsDestructuredBindings[key];\n    bindingMetadata[local] = BindingTypes.PROPS;\n  }\n\n  // 7.5 Transform destructured props accesses to __props.xxx\n  if (Object.keys(propsDestructuredBindings).length > 0) {\n    // Build a map from local variable name to prop key\n    const localToPropKey: Record<string, string> = {};\n    for (const key in propsDestructuredBindings) {\n      const { local } = propsDestructuredBindings[key];\n      localToPropKey[local] = key;\n    }\n\n    // Walk the script setup AST and replace accesses\n    for (const node of scriptSetupAst.body) {\n      // Skip the defineProps call itself (already removed)\n      if (node.type === \"ImportDeclaration\") continue;\n\n      walk(node as any, {\n        enter(child: any, parent: any) {\n          if (child.type === \"Identifier\" && localToPropKey[child.name]) {\n            // Check if this is a reference (not a declaration or property key)\n            if (parent) {\n              // Skip if it's a property key in object\n              if (parent.type === \"Property\" && parent.key === child && !parent.computed) {\n                return;\n              }\n              // Skip if it's a member expression property\n              if (\n                parent.type === \"MemberExpression\" &&\n                parent.property === child &&\n                !parent.computed\n              ) {\n                return;\n              }\n              // Skip if it's a function parameter\n              if (\n                (parent.type === \"FunctionDeclaration\" ||\n                  parent.type === \"FunctionExpression\" ||\n                  parent.type === \"ArrowFunctionExpression\") &&\n                parent.params?.includes(child)\n              ) {\n                return;\n              }\n              // Skip if it's a variable declaration id\n              if (parent.type === \"VariableDeclarator\" && parent.id === child) {\n                return;\n              }\n              // Skip if it's part of object pattern\n              if (parent.type === \"ObjectProperty\" && parent.value === child && parent.shorthand) {\n                // For shorthand like { foo } in destructure, skip\n                const grandParent = (this as any).parent;\n                if (grandParent && grandParent.type === \"ObjectPattern\") {\n                  return;\n                }\n              }\n            }\n\n            const propKey = localToPropKey[child.name];\n            const replacement = `__props.${propKey}`;\n            s.overwrite(child.start! + startOffset, child.end! + startOffset, replacement);\n          }\n        },\n      });\n    }\n  }\n\n  for (const [key, { imported, source }] of Object.entries(userImports)) {\n    bindingMetadata[key] =\n      imported === \"*\" ||\n      (imported === \"default\" && source.endsWith(\".vue\")) ||\n      source === \"vue\" ||\n      source === \"chibivue\"\n        ? BindingTypes.SETUP_CONST\n        : BindingTypes.SETUP_MAYBE_REF;\n  }\n  for (const key in scriptBindings) {\n    bindingMetadata[key] = scriptBindings[key];\n  }\n  for (const key in setupBindings) {\n    bindingMetadata[key] = setupBindings[key];\n  }\n\n  // 9. finalize setup() argument signature\n  let args = `__props`;\n  if (propsIdentifier) {\n    s.prependLeft(startOffset, `\\nconst ${propsIdentifier} = __props;\\n`);\n  }\n  const destructureElements: string[] = [];\n  if (emitIdentifier) {\n    destructureElements.push(emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`);\n  }\n  if (destructureElements.length) {\n    args += `, { ${destructureElements.join(\", \")} }`;\n  }\n\n  // 10. generate return statement\n  let returned;\n  if (options.inlineTemplate) {\n    if (sfc.template) {\n      const { code, preamble } = compileTemplate({\n        source: sfc.template.content.trim(),\n        compilerOptions: { inline: true, bindingMetadata },\n      });\n\n      if (preamble) {\n        s.prepend(preamble);\n      }\n      returned = code;\n    } else {\n      returned = `() => {}`;\n    }\n  }\n  s.appendRight(endOffset, `\\nreturn ${returned}\\n`);\n\n  // 11. finalize default export\n  let runtimeOptions = ``;\n  if (propsRuntimeDecl) {\n    let declCode = scriptSetup.content.slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!).trim();\n\n    // Merge defaults from destructuring\n    const hasDefaults = Object.values(propsDestructuredBindings).some((b) => b.default);\n    if (hasDefaults) {\n      // Parse the runtime decl and merge defaults\n      declCode = mergePropsDefaults(declCode, propsDestructuredBindings);\n    }\n\n    runtimeOptions += `\\n  props: ${declCode},`;\n  } else if (propsTypeDecl) {\n    // Generate runtime props from type declaration\n    const declCode = genRuntimePropsFromType(propsTypeDecl, propsDestructuredBindings);\n    runtimeOptions += `\\n  props: ${declCode},`;\n  }\n  if (emitsRuntimeDecl) {\n    runtimeOptions += `\\n  emits: ${scriptSetup.content\n      .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)\n      .trim()},`;\n  } else if (emitsTypeDecl) {\n    // Generate runtime emits from type declaration\n    const declCode = genRuntimeEmitsFromType(emitsTypeDecl);\n    runtimeOptions += `\\n  emits: ${declCode},`;\n  }\n\n  if (defaultExport) {\n    s.prependLeft(\n      startOffset,\n      `\\nexport default /*#__PURE__*/Object.assign(${\n        defaultExport ? `${DEFAULT_VAR}, ` : \"\"\n      }{ setup(${args}) {\\n`,\n    );\n    s.appendRight(endOffset, `})`);\n  } else {\n    s.prependLeft(startOffset, `\\nexport default {\\n${runtimeOptions}\\nsetup(${args}) {\\n`);\n    s.appendRight(endOffset, `}}`);\n  }\n\n  s.trim();\n\n  return {\n    ...scriptSetup,\n    content: s.toString(),\n    imports: userImports,\n    scriptAst: scriptAst.body,\n    scriptSetupAst: scriptSetupAst.body,\n  };\n}\n\nfunction registerBinding(\n  bindings: Record<string, BindingTypes>,\n  node: Identifier,\n  type: BindingTypes,\n) {\n  bindings[node.name] = type;\n}\n\nfunction walkDeclaration(\n  node: Declaration,\n  bindings: Record<string, BindingTypes>,\n  userImportAliases: Record<string, string> = {},\n) {\n  if (node.type === \"VariableDeclaration\") {\n    const isConst = node.kind === \"const\";\n\n    for (const { id, init } of node.declarations) {\n      if (id.type === \"Identifier\") {\n        let bindingType;\n        const userReactiveBinding = userImportAliases[\"reactive\"];\n        if (isConst && isStaticNode(init!)) {\n          bindingType = BindingTypes.LITERAL_CONST;\n        } else if (isCallOf(init, userReactiveBinding)) {\n          // treat reactive() calls as let since it's meant to be mutable\n          bindingType = BindingTypes.SETUP_REACTIVE_CONST;\n        } else if (isConst && canNeverBeRef(init!, userReactiveBinding)) {\n          bindingType = BindingTypes.SETUP_CONST;\n        } else if (isConst) {\n          if (isCallOf(init, userImportAliases[\"ref\"])) {\n            bindingType = BindingTypes.SETUP_REF;\n          } else {\n            bindingType = BindingTypes.SETUP_MAYBE_REF;\n          }\n        } else {\n          bindingType = BindingTypes.SETUP_LET;\n        }\n        registerBinding(bindings, id, bindingType);\n      } else {\n        if (id.type === \"ObjectPattern\") {\n          walkObjectPattern(id, bindings, isConst);\n        } else if (id.type === \"ArrayPattern\") {\n          walkArrayPattern(id, bindings, isConst);\n        }\n      }\n    }\n  } else if (node.type === \"FunctionDeclaration\" || node.type === \"ClassDeclaration\") {\n    bindings[node.id!.name] = BindingTypes.SETUP_CONST;\n  }\n}\n\nfunction walkObjectPattern(\n  node: ObjectPattern,\n  bindings: Record<string, BindingTypes>,\n  isConst: boolean,\n  isDefineCall = false,\n) {\n  for (const p of node.properties) {\n    if (p.type === \"ObjectProperty\") {\n      if (p.key.type === \"Identifier\" && p.key === p.value) {\n        // shorthand: const { x } = ...\n        const type = isDefineCall\n          ? BindingTypes.SETUP_CONST\n          : isConst\n            ? BindingTypes.SETUP_MAYBE_REF\n            : BindingTypes.SETUP_LET;\n        registerBinding(bindings, p.key, type);\n      } else {\n        walkPattern(p.value, bindings, isConst, isDefineCall);\n      }\n    } else {\n      // ...rest\n      // argument can only be identifier when destructuring\n      const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET;\n      registerBinding(bindings, p.argument as Identifier, type);\n    }\n  }\n}\n\nfunction walkArrayPattern(\n  node: ArrayPattern,\n  bindings: Record<string, BindingTypes>,\n  isConst: boolean,\n  isDefineCall = false,\n) {\n  for (const e of node.elements) {\n    e && walkPattern(e, bindings, isConst, isDefineCall);\n  }\n}\n\nfunction walkPattern(\n  node: Node,\n  bindings: Record<string, BindingTypes>,\n  isConst: boolean,\n  isDefineCall = false,\n) {\n  if (node.type === \"Identifier\") {\n    const type = isDefineCall\n      ? BindingTypes.SETUP_CONST\n      : isConst\n        ? BindingTypes.SETUP_MAYBE_REF\n        : BindingTypes.SETUP_LET;\n    registerBinding(bindings, node, type);\n  } else if (node.type === \"RestElement\") {\n    // argument can only be identifier when destructuring\n    const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET;\n    registerBinding(bindings, node.argument as Identifier, type);\n  } else if (node.type === \"ObjectPattern\") {\n    walkObjectPattern(node, bindings, isConst);\n  } else if (node.type === \"ArrayPattern\") {\n    walkArrayPattern(node, bindings, isConst);\n  } else if (node.type === \"AssignmentPattern\") {\n    if (node.left.type === \"Identifier\") {\n      const type = isDefineCall\n        ? BindingTypes.SETUP_CONST\n        : isConst\n          ? BindingTypes.SETUP_MAYBE_REF\n          : BindingTypes.SETUP_LET;\n      registerBinding(bindings, node.left, type);\n    } else {\n      walkPattern(node.left, bindings, isConst);\n    }\n  }\n}\n\nfunction analyzeScriptBindings(ast: Statement[]): BindingMetadata {\n  for (const node of ast) {\n    if (node.type === \"ExportDefaultDeclaration\" && node.declaration.type === \"ObjectExpression\") {\n      return analyzeBindingsFromOptions(node.declaration);\n    }\n  }\n  return {};\n}\n\nfunction analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {\n  const bindings: BindingMetadata = {};\n  Object.defineProperty(bindings, \"__isScriptSetup\", {\n    enumerable: false,\n    value: false,\n  });\n\n  for (const property of node.properties) {\n    if (\n      property.type === \"ObjectProperty\" &&\n      !property.computed &&\n      property.key.type === \"Identifier\"\n    ) {\n      // props\n      if (property.value.type === \"ObjectExpression\" && property.key.name === \"props\") {\n        for (const key of getObjectExpressionKeys(property.value)) {\n          bindings[key] = BindingTypes.PROPS;\n        }\n      }\n\n      // computed & methods\n      if (\n        property.value.type === \"ObjectExpression\" &&\n        (property.key.name === \"computed\" || property.key.name === \"methods\")\n      ) {\n        // methods: { foo() {} }\n        // computed: { foo() {} }\n        for (const key of getObjectExpressionKeys(property.value)) {\n          bindings[key] = BindingTypes.OPTIONS;\n        }\n      }\n    }\n\n    // setup & data\n    else if (\n      property.type === \"ObjectMethod\" &&\n      property.key.type === \"Identifier\" &&\n      (property.key.name === \"setup\" || property.key.name === \"data\")\n    ) {\n      for (const bodyItem of property.body.body) {\n        // setup() {\n        //   return {\n        //     foo: null\n        //   }\n        // }\n        if (\n          bodyItem.type === \"ReturnStatement\" &&\n          bodyItem.argument &&\n          bodyItem.argument.type === \"ObjectExpression\"\n        ) {\n          for (const key of getObjectExpressionKeys(bodyItem.argument)) {\n            bindings[key] =\n              property.key.name === \"setup\" ? BindingTypes.SETUP_MAYBE_REF : BindingTypes.DATA;\n          }\n        }\n      }\n    }\n  }\n\n  return bindings;\n}\n\nfunction getObjectExpressionKeys(node: ObjectExpression): string[] {\n  const keys: string[] = [];\n  for (const prop of node.properties) {\n    if (prop.type === \"SpreadElement\") continue;\n    const key = resolveObjectKey(prop.key, prop.computed);\n    if (key) keys.push(String(key));\n  }\n  return keys;\n}\n\nexport function resolveObjectKey(node: Node, computed: boolean): string | number | undefined {\n  switch (node.type) {\n    case \"StringLiteral\":\n    case \"NumericLiteral\":\n      return node.value;\n    case \"Identifier\":\n      if (!computed) return node.name;\n  }\n  return undefined;\n}\n\nfunction isCallOf(\n  node: Node | null | undefined,\n  test: string | ((id: string) => boolean) | null | undefined,\n): node is CallExpression {\n  return !!(\n    node &&\n    test &&\n    node.type === \"CallExpression\" &&\n    node.callee.type === \"Identifier\" &&\n    (typeof test === \"string\" ? node.callee.name === test : test(node.callee.name))\n  );\n}\n\nfunction canNeverBeRef(node: Node, userReactiveImport?: string): boolean {\n  if (isCallOf(node, userReactiveImport)) {\n    return true;\n  }\n  switch (node.type) {\n    case \"UnaryExpression\":\n    case \"BinaryExpression\":\n    case \"ArrayExpression\":\n    case \"ObjectExpression\":\n    case \"FunctionExpression\":\n    case \"ArrowFunctionExpression\":\n    case \"UpdateExpression\":\n    case \"ClassExpression\":\n    case \"TaggedTemplateExpression\":\n      return true;\n    case \"SequenceExpression\":\n      return canNeverBeRef(node.expressions[node.expressions.length - 1], userReactiveImport);\n    default:\n      if (isLiteralNode(node)) {\n        return true;\n      }\n      return false;\n  }\n}\n\nfunction isStaticNode(node: Node): boolean {\n  switch (node.type) {\n    case \"UnaryExpression\": // void 0, !true\n      return isStaticNode(node.argument);\n\n    case \"LogicalExpression\": // 1 > 2\n    case \"BinaryExpression\": // 1 + 2\n      return isStaticNode(node.left) && isStaticNode(node.right);\n\n    case \"ConditionalExpression\": {\n      // 1 ? 2 : 3\n      return (\n        isStaticNode(node.test) && isStaticNode(node.consequent) && isStaticNode(node.alternate)\n      );\n    }\n\n    case \"SequenceExpression\": // (1, 2)\n    case \"TemplateLiteral\": // `foo${1}`\n      return node.expressions.every((expr) => isStaticNode(expr));\n\n    case \"ParenthesizedExpression\": // (1)\n    case \"TSNonNullExpression\": // 1!\n    case \"TSAsExpression\": // 1 as number\n    case \"TSTypeAssertion\": // (<number>2)\n      return isStaticNode(node.expression);\n\n    default:\n      if (isLiteralNode(node)) {\n        return true;\n      }\n      return false;\n  }\n}\n\nfunction isLiteralNode(node: Node) {\n  return node.type.endsWith(\"Literal\");\n}\n\ninterface PropsDestructureBinding {\n  local: string;\n  default?: string;\n}\n\nfunction extractPropsKeysFromType(typeDecl: TSTypeLiteral): string[] {\n  const keys: string[] = [];\n  for (const member of typeDecl.members) {\n    if (member.type === \"TSPropertySignature\" && member.key.type === \"Identifier\") {\n      keys.push(member.key.name);\n    }\n  }\n  return keys;\n}\n\nfunction genRuntimePropsFromType(\n  typeDecl: TSTypeLiteral,\n  destructuredBindings: Record<string, PropsDestructureBinding>,\n): string {\n  const props: string[] = [];\n\n  for (const member of typeDecl.members) {\n    if (member.type === \"TSPropertySignature\" && member.key.type === \"Identifier\") {\n      const key = member.key.name;\n      const isOptional = !!member.optional;\n      const typeAnnotation = member.typeAnnotation?.typeAnnotation;\n\n      // Get runtime type\n      const runtimeTypes = typeAnnotation ? resolveRuntimeType(typeAnnotation) : [\"null\"];\n      const typeStr = runtimeTypes.length === 1 ? runtimeTypes[0] : `[${runtimeTypes.join(\", \")}]`;\n\n      // Check for default value from destructuring\n      const binding = destructuredBindings[key];\n      const hasDefault = binding?.default;\n\n      if (hasDefault) {\n        props.push(`${key}: { type: ${typeStr}, required: false, default: ${binding.default} }`);\n      } else if (isOptional) {\n        props.push(`${key}: { type: ${typeStr}, required: false }`);\n      } else {\n        props.push(`${key}: { type: ${typeStr}, required: true }`);\n      }\n    }\n  }\n\n  return `{ ${props.join(\", \")} }`;\n}\n\nfunction resolveRuntimeType(node: Node): string[] {\n  switch (node.type) {\n    case \"TSStringKeyword\":\n      return [\"String\"];\n    case \"TSNumberKeyword\":\n      return [\"Number\"];\n    case \"TSBooleanKeyword\":\n      return [\"Boolean\"];\n    case \"TSArrayType\":\n      return [\"Array\"];\n    case \"TSFunctionType\":\n      return [\"Function\"];\n    case \"TSObjectKeyword\":\n    case \"TSTypeLiteral\":\n      return [\"Object\"];\n    case \"TSUnionType\": {\n      const types: string[] = [];\n      for (const t of node.types) {\n        // Skip null/undefined in union\n        if (t.type === \"TSNullKeyword\" || t.type === \"TSUndefinedKeyword\") {\n          continue;\n        }\n        types.push(...resolveRuntimeType(t));\n      }\n      return types.length > 0 ? types : [\"null\"];\n    }\n    case \"TSTypeReference\":\n      if (node.typeName.type === \"Identifier\") {\n        const name = node.typeName.name;\n        // Built-in type mapping\n        if (name === \"Array\") return [\"Array\"];\n        if (name === \"Function\") return [\"Function\"];\n        if (name === \"Object\") return [\"Object\"];\n        if (name === \"String\") return [\"String\"];\n        if (name === \"Number\") return [\"Number\"];\n        if (name === \"Boolean\") return [\"Boolean\"];\n        // Custom types default to null (will be validated at runtime)\n        return [\"null\"];\n      }\n      return [\"Object\"];\n    default:\n      return [\"null\"];\n  }\n}\n\nfunction genRuntimeEmitsFromType(typeDecl: TSTypeLiteral): string {\n  const events: string[] = [];\n\n  for (const member of typeDecl.members) {\n    // Handle call signatures: { (e: 'change', value: string): void }\n    if (member.type === \"TSCallSignatureDeclaration\") {\n      const params = member.parameters;\n      if (params && params.length > 0) {\n        const firstParam = params[0];\n        if (\n          firstParam.type === \"Identifier\" &&\n          firstParam.typeAnnotation &&\n          \"typeAnnotation\" in firstParam.typeAnnotation\n        ) {\n          const typeAnn = firstParam.typeAnnotation.typeAnnotation as any;\n          if (typeAnn.type === \"TSLiteralType\" && typeAnn.literal.type === \"StringLiteral\") {\n            events.push(`\"${typeAnn.literal.value}\"`);\n          }\n        }\n      }\n    }\n    // Handle property signatures: { change: (value: string) => void }\n    else if (member.type === \"TSPropertySignature\" && member.key.type === \"Identifier\") {\n      events.push(`\"${member.key.name}\"`);\n    }\n  }\n\n  return `[${events.join(\", \")}]`;\n}\n\nfunction mergePropsDefaults(\n  declCode: string,\n  destructuredBindings: Record<string, PropsDestructureBinding>,\n): string {\n  // Simple implementation: parse the object and merge defaults\n  // For a more robust implementation, use Babel to transform the AST\n  try {\n    const ast = _parse(`(${declCode})`, { sourceType: \"module\" }).program.body[0];\n    if (ast.type !== \"ExpressionStatement\") return declCode;\n\n    const expr = ast.expression;\n    if (expr.type !== \"ObjectExpression\") return declCode;\n\n    // Build the merged props object\n    const props: string[] = [];\n    const processedKeys = new Set<string>();\n\n    for (const prop of expr.properties) {\n      if (prop.type === \"SpreadElement\") {\n        // Keep spread elements as-is\n        props.push(declCode.slice(prop.start! - 1, prop.end! - 1));\n        continue;\n      }\n\n      const keyNode = prop.key;\n      let key: string;\n      if (keyNode.type === \"Identifier\") {\n        key = keyNode.name;\n      } else if (keyNode.type === \"StringLiteral\") {\n        key = keyNode.value;\n      } else {\n        // Keep computed keys as-is\n        props.push(declCode.slice(prop.start! - 1, prop.end! - 1));\n        continue;\n      }\n\n      processedKeys.add(key);\n      const binding = destructuredBindings[key];\n\n      // Skip ObjectMethod - only process ObjectProperty\n      if (prop.type !== \"ObjectProperty\") {\n        props.push(declCode.slice(prop.start! - 1, prop.end! - 1));\n        continue;\n      }\n\n      if (binding?.default) {\n        // Has default value from destructuring\n        const valueCode = declCode.slice(prop.value.start! - 1, prop.value.end! - 1);\n\n        // Check if value is already an object with type\n        if (prop.value.type === \"ObjectExpression\") {\n          // { type: String } -> { type: String, default: value }\n          const hasDefault = prop.value.properties.some(\n            (p: any) =>\n              p.type === \"ObjectProperty\" &&\n              p.key.type === \"Identifier\" &&\n              p.key.name === \"default\",\n          );\n          if (!hasDefault) {\n            props.push(`${key}: { ...${valueCode}, default: ${binding.default} }`);\n          } else {\n            props.push(`${key}: ${valueCode}`);\n          }\n        } else {\n          // String -> { type: String, default: value }\n          props.push(`${key}: { type: ${valueCode}, default: ${binding.default} }`);\n        }\n      } else {\n        // No default, keep as-is\n        const valueCode = declCode.slice(prop.value.start! - 1, prop.value.end! - 1);\n        props.push(`${key}: ${valueCode}`);\n      }\n    }\n\n    return `{ ${props.join(\", \")} }`;\n  } catch {\n    // If parsing fails, return original\n    return declCode;\n  }\n}\n"
  },
  {
    "path": "impl/compiler-sfc/src/compileStyle.ts",
    "content": "export interface SFCStyleCompileOptions {\n  source: string;\n  filename: string;\n  id: string;\n  scoped?: boolean;\n}\n\nexport interface SFCStyleCompileResults {\n  code: string;\n}\n\nexport function compileStyle(options: SFCStyleCompileOptions): SFCStyleCompileResults {\n  const { source, id, scoped } = options;\n\n  if (!scoped) {\n    return { code: source };\n  }\n\n  const scopeId = `data-v-${id}`;\n  const code = processScoped(source, scopeId);\n\n  return { code };\n}\n\n/**\n * Process scoped CSS by adding scope attribute selector to each rule\n */\nfunction processScoped(css: string, scopeId: string): string {\n  // Simple CSS parser that handles basic cases\n  // For production, you'd want to use PostCSS\n  const result: string[] = [];\n  let i = 0;\n\n  while (i < css.length) {\n    // Skip whitespace\n    const wsStart = i;\n    while (i < css.length && /\\s/.test(css[i])) i++;\n    if (i > wsStart) {\n      result.push(css.slice(wsStart, i));\n    }\n\n    if (i >= css.length) break;\n\n    // Check for comments\n    if (css[i] === \"/\" && css[i + 1] === \"*\") {\n      const commentEnd = css.indexOf(\"*/\", i + 2);\n      if (commentEnd === -1) {\n        result.push(css.slice(i));\n        break;\n      }\n      result.push(css.slice(i, commentEnd + 2));\n      i = commentEnd + 2;\n      continue;\n    }\n\n    // Check for at-rules\n    if (css[i] === \"@\") {\n      const atRule = parseAtRule(css, i);\n      if (atRule.name === \"media\" || atRule.name === \"supports\") {\n        // Process nested rules inside media/supports\n        result.push(atRule.prelude);\n        result.push(\"{\");\n        const nestedContent = processScoped(atRule.body, scopeId);\n        result.push(nestedContent);\n        result.push(\"}\");\n      } else if (atRule.name === \"keyframes\" || atRule.name === \"-webkit-keyframes\") {\n        // Keep keyframes as-is\n        result.push(css.slice(i, atRule.end));\n      } else {\n        // Other at-rules (like @import, @charset)\n        result.push(css.slice(i, atRule.end));\n      }\n      i = atRule.end;\n      continue;\n    }\n\n    // Parse selector and declaration block\n    const ruleStart = i;\n    const braceIndex = css.indexOf(\"{\", i);\n    if (braceIndex === -1) {\n      result.push(css.slice(i));\n      break;\n    }\n\n    const selector = css.slice(i, braceIndex).trim();\n    i = braceIndex + 1;\n\n    // Find matching closing brace\n    let braceCount = 1;\n    const bodyStart = i;\n    while (i < css.length && braceCount > 0) {\n      if (css[i] === \"{\") braceCount++;\n      else if (css[i] === \"}\") braceCount--;\n      i++;\n    }\n    const bodyEnd = i - 1;\n    const body = css.slice(bodyStart, bodyEnd);\n\n    // Transform selector\n    const scopedSelector = transformSelector(selector, scopeId);\n    result.push(scopedSelector);\n    result.push(\" {\");\n    result.push(body);\n    result.push(\"}\");\n  }\n\n  return result.join(\"\");\n}\n\ninterface AtRuleInfo {\n  name: string;\n  prelude: string;\n  body: string;\n  end: number;\n}\n\nfunction parseAtRule(css: string, start: number): AtRuleInfo {\n  let i = start + 1; // Skip @\n\n  // Get at-rule name\n  const nameStart = i;\n  while (i < css.length && /[a-zA-Z-]/.test(css[i])) i++;\n  const name = css.slice(nameStart, i);\n\n  // Get prelude (everything until { or ;)\n  const preludeStart = start;\n  while (i < css.length && css[i] !== \"{\" && css[i] !== \";\") i++;\n\n  if (css[i] === \";\") {\n    // At-rule without block (like @import)\n    return {\n      name,\n      prelude: css.slice(preludeStart, i + 1),\n      body: \"\",\n      end: i + 1,\n    };\n  }\n\n  const prelude = css.slice(preludeStart, i);\n  i++; // Skip {\n\n  // Find matching }\n  let braceCount = 1;\n  const bodyStart = i;\n  while (i < css.length && braceCount > 0) {\n    if (css[i] === \"{\") braceCount++;\n    else if (css[i] === \"}\") braceCount--;\n    i++;\n  }\n\n  const body = css.slice(bodyStart, i - 1);\n\n  return { name, prelude, body, end: i };\n}\n\n/**\n * Transform a selector to add scope attribute\n */\nfunction transformSelector(selector: string, scopeId: string): string {\n  // Split by comma to handle multiple selectors\n  return selector\n    .split(\",\")\n    .map((s) => transformSingleSelector(s.trim(), scopeId))\n    .join(\", \");\n}\n\nfunction transformSingleSelector(selector: string, scopeId: string): string {\n  // Handle :deep() pseudo-class\n  if (selector.includes(\":deep(\")) {\n    return selector.replace(/:deep\\(([^)]+)\\)/g, (_, inner) => {\n      // Add scope to the part before :deep, then the inner part without scope\n      const parts = selector.split(/:deep\\([^)]+\\)/);\n      const before = parts[0].trim();\n      if (before) {\n        return `${before}[${scopeId}] ${inner}`;\n      }\n      return `[${scopeId}] ${inner}`;\n    });\n  }\n\n  // Handle :slotted() pseudo-class\n  if (selector.includes(\":slotted(\")) {\n    return selector.replace(/:slotted\\(([^)]+)\\)/g, (_, inner) => {\n      return `${inner}[${scopeId}-s]`;\n    });\n  }\n\n  // Handle :global() pseudo-class - remove the wrapper\n  if (selector.includes(\":global(\")) {\n    return selector.replace(/:global\\(([^)]+)\\)/g, \"$1\");\n  }\n\n  // Handle pseudo-elements and pseudo-classes\n  // Add scope before pseudo-element/class\n  const pseudoElementMatch = selector.match(/(::?[a-zA-Z-]+(?:\\([^)]*\\))?)$/);\n  if (pseudoElementMatch) {\n    const pseudoPart = pseudoElementMatch[1];\n    const mainPart = selector.slice(0, -pseudoPart.length);\n    return `${mainPart}[${scopeId}]${pseudoPart}`;\n  }\n\n  // Simple case: add [data-v-xxx] at the end\n  return `${selector}[${scopeId}]`;\n}\n"
  },
  {
    "path": "impl/compiler-sfc/src/compileTemplate.ts",
    "content": "import type {\n  CodegenResult,\n  CompilerOptions,\n  ParserOptions,\n  RootNode,\n} from \"@chibivue/compiler-core\";\nimport * as CompilerDOM from \"@chibivue/compiler-dom\";\nimport * as CompilerSSR from \"@chibivue/compiler-ssr\";\n\nexport interface TemplateCompiler {\n  compile(template: string, options: CompilerOptions): CodegenResult;\n  parse(template: string, options: ParserOptions): RootNode;\n}\n\nexport interface SFCTemplateCompileResults {\n  code: string;\n  source: string;\n  ast?: RootNode;\n  preamble?: string;\n}\n\nexport interface SFCTemplateCompileOptions {\n  source: string;\n  compiler?: TemplateCompiler;\n  compilerOptions?: CompilerOptions;\n  id?: string;\n  scoped?: boolean;\n  ssr?: boolean;\n  /**\n   * Enable Vapor mode compilation.\n   * When combined with ssr: true, uses compiler-ssr with __vapor flag.\n   */\n  vapor?: boolean;\n}\n\nexport function compileTemplate({\n  source,\n  compiler,\n  compilerOptions,\n  id,\n  scoped,\n  ssr = false,\n  vapor = false,\n}: SFCTemplateCompileOptions): SFCTemplateCompileResults {\n  // Determine the compiler to use:\n  // - Vapor + SSR: Use compiler-ssr (generates VNode SSR code)\n  // - SSR only: Use compiler-ssr\n  // - Vapor only: Use compiler-dom (vapor compilation is handled separately)\n  // - Default: Use compiler-dom\n  //\n  // Note: In Vapor SSR mode, the server-side rendering uses standard VNode SSR.\n  // The __vapor flag is used to indicate that hydration should use Vapor mode.\n  const defaultCompiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM;\n\n  let { code, ast, preamble } = (compiler || defaultCompiler).compile(source, {\n    ...compilerOptions,\n    isBrowser: false,\n    scopeId: scoped ? id : undefined,\n    ssr,\n  });\n\n  // For Vapor + SSR mode, the rendered code needs to indicate it's a Vapor component\n  // This flag is used during hydration to use Vapor-specific hydration logic\n  if (vapor && ssr) {\n    // Add __vapor marker to the component definition for hydration\n    // The hydration logic will detect this and use createVaporSSRApp\n    code = code.replace(\n      /export (function|const) ssrRender/,\n      \"export const __vapor = true;\\nexport $1 ssrRender\",\n    );\n  }\n\n  return { code: code, ast, source, preamble };\n}\n"
  },
  {
    "path": "impl/compiler-sfc/src/index.ts",
    "content": "export * from \"./parse\";\nexport { compileScript } from \"./compileScript\";\nexport { compileStyle } from \"./compileStyle\";\nexport { compileTemplate } from \"./compileTemplate\";\nexport { rewriteDefault } from \"./rewriteDefault\";\n\nexport type { SFCStyleCompileOptions, SFCStyleCompileResults } from \"./compileStyle\";\n\nexport type {\n  SFCTemplateCompileResults,\n  SFCTemplateCompileOptions,\n  TemplateCompiler,\n} from \"./compileTemplate\";\n"
  },
  {
    "path": "impl/compiler-sfc/src/parse.ts",
    "content": "import type { BindingMetadata, ElementNode, SourceLocation } from \"@chibivue/compiler-core\";\nimport { NodeTypes } from \"@chibivue/compiler-core\";\nimport * as CompilerDOM from \"@chibivue/compiler-dom\";\n\nimport type { ImportBinding } from \"./compileScript\";\nimport type { TemplateCompiler } from \"./compileTemplate\";\n\nexport const DEFAULT_FILENAME = \"anonymous.vue\";\n\nexport interface SFCParseOptions {\n  filename?: string;\n  sourceRoot?: string;\n  compiler?: TemplateCompiler;\n}\n\nexport interface SFCBlock {\n  type: string;\n  content: string;\n  loc: SourceLocation;\n  attrs: Record<string, string | true>;\n}\n\nexport interface SFCTemplateBlock extends SFCBlock {\n  type: \"template\";\n}\n\nexport interface SFCScriptBlock extends SFCBlock {\n  type: \"script\";\n  setup?: string | boolean;\n  bindings?: BindingMetadata;\n  imports?: Record<string, ImportBinding>;\n  scriptAst?: import(\"@babel/types\").Statement[];\n  scriptSetupAst?: import(\"@babel/types\").Statement[];\n}\n\nexport interface SFCStyleBlock extends SFCBlock {\n  type: \"style\";\n  scoped?: boolean;\n}\n\nexport interface SFCDescriptor {\n  id: string;\n  filename: string;\n  source: string;\n  template: SFCTemplateBlock | null;\n  script: SFCScriptBlock | null;\n  scriptSetup: SFCScriptBlock | null;\n  styles: SFCStyleBlock[];\n}\n\nexport interface SFCParseResult {\n  descriptor: SFCDescriptor;\n}\n\nexport function parse(\n  source: string,\n  { filename = DEFAULT_FILENAME, compiler = CompilerDOM }: SFCParseOptions = {},\n): SFCParseResult {\n  const descriptor: SFCDescriptor = {\n    id: undefined!,\n    filename,\n    source,\n    template: null,\n    script: null,\n    scriptSetup: null,\n    styles: [],\n  };\n\n  const ast = compiler.parse(source, {});\n  ast.children.forEach((node) => {\n    if (node.type !== NodeTypes.ELEMENT) return;\n\n    switch (node.tag) {\n      case \"template\": {\n        descriptor.template = createBlock(node, source) as SFCTemplateBlock;\n        break;\n      }\n      case \"script\": {\n        const scriptBlock = createBlock(node, source) as SFCScriptBlock;\n        const isSetup = !!scriptBlock.attrs.setup;\n        if (isSetup && !descriptor.scriptSetup) {\n          descriptor.scriptSetup = scriptBlock;\n        }\n        if (!isSetup && !descriptor.script) {\n          descriptor.script = scriptBlock;\n        }\n        break;\n      }\n      case \"style\": {\n        descriptor.styles.push(createBlock(node, source) as SFCStyleBlock);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  });\n\n  return { descriptor };\n}\n\nfunction createBlock(node: ElementNode, source: string): SFCBlock {\n  const type = node.tag;\n  let { start, end } = node.loc;\n  let content = \"\";\n  if (node.children.length) {\n    start = node.children[0].loc.start;\n    end = node.children[node.children.length - 1].loc.end;\n    content = source.slice(start.offset, end.offset);\n  } else {\n    const offset = node.loc.source.indexOf(`</`);\n    if (offset > -1) {\n      start = {\n        line: start.line,\n        column: start.column + offset,\n        offset: start.offset + offset,\n      };\n    }\n    end = { ...start };\n  }\n\n  const attrs: Record<string, string | true> = {};\n\n  const loc = {\n    source: content,\n    start,\n    end,\n  };\n  const block: SFCBlock = {\n    type,\n    content,\n    attrs,\n    loc,\n  };\n\n  node.props.forEach((p) => {\n    if (p.type === NodeTypes.ATTRIBUTE) {\n      attrs[p.name] = p.value ? p.value.content || true : true;\n      if (p.name === \"lang\") {\n        // TODO: parse lang\n      } else if (type === \"style\") {\n        if (p.name === \"scoped\") {\n          (block as SFCStyleBlock).scoped = true;\n        }\n      } else if (type === \"script\" && p.name === \"setup\") {\n        (block as SFCScriptBlock).setup = attrs.setup;\n      }\n    }\n  });\n\n  return block;\n}\n"
  },
  {
    "path": "impl/compiler-sfc/src/rewriteDefault.ts",
    "content": "import { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\n\nconst defaultExportRE = /((?:^|\\n|;)\\s*)export(\\s*)default/;\nconst namedDefaultExportRE = /((?:^|\\n|;)\\s*)export(.+)(?:as)?(\\s*)default/s;\n\nexport function rewriteDefault(input: string, as: string): string {\n  if (!hasDefaultExport(input)) {\n    return input + `\\nconst ${as} = {}`;\n  }\n\n  const replaced: string | undefined = input.replace(defaultExportRE, `$1const ${as} =`);\n\n  if (!hasDefaultExport(replaced)) {\n    return replaced;\n  }\n\n  const s = new MagicString(input);\n  const ast = parse(input, {\n    sourceType: \"module\",\n  }).program.body;\n\n  ast.forEach((node) => {\n    if (node.type === \"ExportDefaultDeclaration\") {\n      if (node.declaration.type === \"ClassDeclaration\") {\n        s.overwrite(node.start!, node.declaration.id?.start!, `class `);\n        s.append(`\\nconst ${as} = ${node.declaration.id?.name}`);\n      } else {\n        s.overwrite(node.start!, node.declaration.start!, `const ${as} = `);\n      }\n    }\n    if (node.type === \"ExportNamedDeclaration\") {\n      for (const specifier of node.specifiers) {\n        if (\n          specifier.type === \"ExportSpecifier\" &&\n          specifier.exported.type === \"Identifier\" &&\n          specifier.exported.name === \"default\"\n        ) {\n          if (node.source) {\n            if (specifier.local.name === \"default\") {\n              const end = specifierEnd(input, specifier.local.end!, node.end!);\n              s.prepend(`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\\n`);\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = __VUE_DEFAULT__`);\n              continue;\n            } else {\n              const end = specifierEnd(input, specifier.exported.end!, node.end!);\n              s.prepend(\n                `import { ${input.slice(\n                  specifier.local.start!,\n                  specifier.local.end!,\n                )} } from '${node.source.value}'\\n`,\n              );\n              s.overwrite(specifier.start!, end, ``);\n              s.append(`\\nconst ${as} = ${specifier.local.name}`);\n              continue;\n            }\n          }\n          const end = specifierEnd(input, specifier.end!, node.end!);\n          s.overwrite(specifier.start!, end, ``);\n          s.append(`\\nconst ${as} = ${specifier.local.name}`);\n        }\n      }\n    }\n  });\n\n  return s.toString();\n}\n\nexport function hasDefaultExport(input: string): boolean {\n  return defaultExportRE.test(input) || namedDefaultExportRE.test(input);\n}\n\nfunction specifierEnd(input: string, end: number, nodeEnd: number | null) {\n  // export { default   , foo } ...\n  let hasCommas = false;\n  let oldEnd = end;\n  while (end < nodeEnd!) {\n    if (/\\s/.test(input.charAt(end))) {\n      end++;\n    } else if (input.charAt(end) === \",\") {\n      end++;\n      hasCommas = true;\n      break;\n    } else if (input.charAt(end) === \"}\") {\n      break;\n    }\n  }\n  return hasCommas ? end : oldEnd;\n}\n"
  },
  {
    "path": "impl/compiler-ssr/package.json",
    "content": "{\n  \"name\": \"@chibivue/compiler-ssr\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/compiler-ssr/src/index.ts",
    "content": "import {\n  type CodegenResult,\n  type CompilerOptions,\n  type RootNode,\n  baseParse,\n  generate,\n  transform,\n  transformBind,\n  transformExpression,\n} from \"@chibivue/compiler-core\";\nimport { parserOptions } from \"@chibivue/compiler-dom\";\nimport { ssrCodegenTransform } from \"./ssrCodegenTransform\";\nimport { ssrTransformElement } from \"./transforms/ssrTransformElement\";\nimport { ssrTransformComponent } from \"./transforms/ssrTransformComponent\";\nimport { ssrTransformIf } from \"./transforms/ssrVIf\";\nimport { ssrTransformFor } from \"./transforms/ssrVFor\";\n\nexport function compile(source: string | RootNode, options: CompilerOptions = {}): CodegenResult {\n  options = {\n    ...options,\n    ...parserOptions,\n    ssr: true,\n    // always prefix since compiler-ssr doesn't have size concern\n    prefixIdentifiers: true,\n    // disable optimizations that are unnecessary for ssr\n    hoistStatic: false,\n  };\n\n  const ast = typeof source === \"string\" ? baseParse(source, options) : source;\n\n  transform(ast, {\n    ...options,\n    hoistStatic: false,\n    nodeTransforms: [\n      ssrTransformIf,\n      ssrTransformFor,\n      transformExpression,\n      ssrTransformElement,\n      ssrTransformComponent,\n      ...(options.nodeTransforms || []),\n    ],\n    directiveTransforms: {\n      bind: transformBind,\n      ...(options.directiveTransforms || {}),\n    },\n  });\n\n  // traverse the template AST and convert into SSR codegen AST\n  // by replacing ast.codegenNode.\n  ssrCodegenTransform(ast, options);\n\n  return generate(ast, options);\n}\n\nexport { ssrCodegenTransform } from \"./ssrCodegenTransform\";\nexport * from \"./runtimeHelpers\";\nexport { parse } from \"@chibivue/compiler-dom\";\n"
  },
  {
    "path": "impl/compiler-ssr/src/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"@chibivue/compiler-dom\";\n\nexport const SSR_INTERPOLATE: unique symbol = Symbol(`ssrInterpolate`);\nexport const SSR_RENDER_ATTRS: unique symbol = Symbol(`ssrRenderAttrs`);\nexport const SSR_RENDER_ATTR: unique symbol = Symbol(`ssrRenderAttr`);\nexport const SSR_RENDER_CLASS: unique symbol = Symbol(`ssrRenderClass`);\nexport const SSR_RENDER_STYLE: unique symbol = Symbol(`ssrRenderStyle`);\nexport const SSR_RENDER_DYNAMIC_ATTR: unique symbol = Symbol(`ssrRenderDynamicAttr`);\nexport const SSR_RENDER_LIST: unique symbol = Symbol(`ssrRenderList`);\nexport const SSR_INCLUDE_BOOLEAN_ATTR: unique symbol = Symbol(`ssrIncludeBooleanAttr`);\nexport const SSR_RENDER_COMPONENT: unique symbol = Symbol(`ssrRenderComponent`);\nexport const SSR_RENDER_VNODE: unique symbol = Symbol(`ssrRenderVNode`);\n\nexport const ssrHelpers: Record<symbol, string> = {\n  [SSR_INTERPOLATE]: `ssrInterpolate`,\n  [SSR_RENDER_ATTRS]: `ssrRenderAttrs`,\n  [SSR_RENDER_ATTR]: `ssrRenderAttr`,\n  [SSR_RENDER_CLASS]: `ssrRenderClass`,\n  [SSR_RENDER_STYLE]: `ssrRenderStyle`,\n  [SSR_RENDER_DYNAMIC_ATTR]: `ssrRenderDynamicAttr`,\n  [SSR_RENDER_LIST]: `ssrRenderList`,\n  [SSR_INCLUDE_BOOLEAN_ATTR]: `ssrIncludeBooleanAttr`,\n  [SSR_RENDER_COMPONENT]: `ssrRenderComponent`,\n  [SSR_RENDER_VNODE]: `ssrRenderVNode`,\n};\n\n// Note: these are helpers imported from @chibivue/server-renderer\nregisterRuntimeHelpers(ssrHelpers);\n"
  },
  {
    "path": "impl/compiler-ssr/src/ssrCodegenTransform.ts",
    "content": "import {\n  type BlockStatement,\n  type CallExpression,\n  type CompilerOptions,\n  ElementTypes,\n  type IfStatement,\n  type JSChildNode,\n  NodeTypes,\n  type RootNode,\n  type TemplateChildNode,\n  type TemplateLiteral,\n  createBlockStatement,\n  createCallExpression,\n  createTemplateLiteral,\n} from \"@chibivue/compiler-core\";\nimport { escapeHtml, isString } from \"@chibivue/shared\";\nimport { SSR_INTERPOLATE, ssrHelpers } from \"./runtimeHelpers\";\nimport { ssrProcessElement } from \"./transforms/ssrTransformElement\";\nimport { ssrProcessComponent } from \"./transforms/ssrTransformComponent\";\nimport { ssrProcessIf } from \"./transforms/ssrVIf\";\nimport { ssrProcessFor } from \"./transforms/ssrVFor\";\n\nexport interface SSRTransformContext {\n  root: RootNode;\n  options: CompilerOptions;\n  body: (JSChildNode | IfStatement)[];\n  helpers: Set<symbol>;\n  onError: (error: Error) => void;\n  helper<T extends symbol>(name: T): T;\n  pushStringPart(part: TemplateLiteral[\"elements\"][0]): void;\n  pushStatement(statement: IfStatement | CallExpression): void;\n}\n\nfunction createSSRTransformContext(\n  root: RootNode,\n  options: CompilerOptions,\n  helpers: Set<symbol> = new Set(),\n): SSRTransformContext {\n  const body: BlockStatement[\"body\"] = [];\n  let currentString: TemplateLiteral | null = null;\n\n  return {\n    root,\n    options,\n    body,\n    helpers,\n    onError: (e: Error) => {\n      throw e;\n    },\n    helper<T extends symbol>(name: T): T {\n      helpers.add(name);\n      return name;\n    },\n    pushStringPart(part) {\n      if (!currentString) {\n        const currentCall = createCallExpression(`_push`);\n        body.push(currentCall);\n        currentString = createTemplateLiteral([]);\n        currentCall.arguments.push(currentString);\n      }\n      const bufferedElements = currentString.elements;\n      const lastItem = bufferedElements[bufferedElements.length - 1];\n      if (isString(part) && isString(lastItem)) {\n        bufferedElements[bufferedElements.length - 1] += part;\n      } else {\n        bufferedElements.push(part);\n      }\n    },\n    pushStatement(statement) {\n      // close current string\n      currentString = null;\n      body.push(statement);\n    },\n  };\n}\n\nexport function createChildContext(parent: SSRTransformContext): SSRTransformContext {\n  return createSSRTransformContext(parent.root, parent.options, parent.helpers);\n}\n\nexport function ssrCodegenTransform(ast: RootNode, options: CompilerOptions): void {\n  const context = createSSRTransformContext(ast, options);\n\n  const isFragment = ast.children.length > 1 && ast.children.some((c) => c.type !== NodeTypes.TEXT);\n  processChildren(ast, context, isFragment);\n  ast.codegenNode = createBlockStatement(context.body);\n\n  // Finalize helpers\n  ast.ssrHelpers = Array.from(\n    new Set([...Array.from(ast.helpers).filter((h) => h in ssrHelpers), ...context.helpers]),\n  );\n\n  ast.helpers = new Set(Array.from(ast.helpers).filter((h) => !(h in ssrHelpers)));\n}\n\ninterface Container {\n  children: TemplateChildNode[];\n}\n\nexport function processChildren(\n  parent: Container,\n  context: SSRTransformContext,\n  asFragment = false,\n): void {\n  if (asFragment) {\n    context.pushStringPart(`<!--[-->`);\n  }\n  const { children } = parent;\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    switch (child.type) {\n      case NodeTypes.ELEMENT:\n        switch (child.tagType) {\n          case ElementTypes.ELEMENT:\n            ssrProcessElement(child, context);\n            break;\n          case ElementTypes.COMPONENT:\n            ssrProcessComponent(child, context, parent);\n            break;\n          case ElementTypes.TEMPLATE:\n            // process children\n            processChildren(child, context);\n            break;\n        }\n        break;\n      case NodeTypes.TEXT:\n        context.pushStringPart(escapeHtml(child.content));\n        break;\n      case NodeTypes.COMMENT:\n        context.pushStringPart(`<!--${child.content}-->`);\n        break;\n      case NodeTypes.INTERPOLATION:\n        context.pushStringPart(\n          createCallExpression(context.helper(SSR_INTERPOLATE), [child.content]),\n        );\n        break;\n      case NodeTypes.IF:\n        ssrProcessIf(child, context);\n        break;\n      case NodeTypes.FOR:\n        ssrProcessFor(child, context);\n        break;\n      case NodeTypes.IF_BRANCH:\n        // no-op - handled by ssrProcessIf\n        break;\n    }\n  }\n  if (asFragment) {\n    context.pushStringPart(`<!--]-->`);\n  }\n}\n\nexport function processChildrenAsStatement(\n  parent: Container,\n  parentContext: SSRTransformContext,\n  asFragment = false,\n): BlockStatement {\n  const childContext = createChildContext(parentContext);\n  processChildren(parent, childContext, asFragment);\n  return createBlockStatement(childContext.body);\n}\n"
  },
  {
    "path": "impl/compiler-ssr/src/transforms/ssrTransformComponent.ts",
    "content": "import {\n  type ComponentNode,\n  ElementTypes,\n  type NodeTransform,\n  NodeTypes,\n  createCallExpression,\n  createSimpleExpression,\n} from \"@chibivue/compiler-core\";\nimport { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from \"../runtimeHelpers\";\nimport type { SSRTransformContext } from \"../ssrCodegenTransform\";\n\nexport const ssrTransformComponent: NodeTransform = (node, context) => {\n  if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.COMPONENT) {\n    return;\n  }\n\n  return function ssrPostTransformComponent() {\n    // Component SSR is handled at runtime\n    // We just mark that it needs SSR rendering\n  };\n};\n\nexport function ssrProcessComponent(\n  node: ComponentNode,\n  context: SSRTransformContext,\n  parent: { children: any[] },\n): void {\n  const component = node.tag;\n\n  // Create a call to ssrRenderComponent\n  // The component will be rendered at runtime using the SSR renderer\n  const vnodeCall = createCallExpression(context.helper(SSR_RENDER_VNODE), [\n    `_push`,\n    createCallExpression(context.helper(SSR_RENDER_COMPONENT), [\n      createSimpleExpression(`_component_${component}`, false),\n      // props\n      node.props.length\n        ? createSimpleExpression(\n            `{ ${node.props\n              .filter((p) => p.type === NodeTypes.ATTRIBUTE)\n              .map((p) => {\n                const attr = p as { name: string; value?: { content: string } };\n                return `${JSON.stringify(attr.name)}: ${\n                  attr.value ? JSON.stringify(attr.value.content) : \"true\"\n                }`;\n              })\n              .join(\", \")} }`,\n            false,\n          )\n        : createSimpleExpression(`null`, false),\n      // slots - for now, null\n      createSimpleExpression(`null`, false),\n      // parent component\n      `_parent`,\n    ]),\n    `_parent`,\n  ]);\n\n  context.pushStatement(vnodeCall);\n}\n"
  },
  {
    "path": "impl/compiler-ssr/src/transforms/ssrTransformElement.ts",
    "content": "import {\n  type CallExpression,\n  ElementTypes,\n  type NodeTransform,\n  NodeTypes,\n  type PlainElementNode,\n  type TemplateLiteral,\n  createCallExpression,\n  createTemplateLiteral,\n} from \"@chibivue/compiler-core\";\nimport {\n  escapeHtml,\n  isBooleanAttr,\n  isSSRSafeAttrName,\n  propsToAttrMap,\n  isVoidTag,\n} from \"@chibivue/shared\";\nimport {\n  SSR_INCLUDE_BOOLEAN_ATTR,\n  SSR_RENDER_ATTR,\n  SSR_RENDER_ATTRS,\n  SSR_RENDER_CLASS,\n  SSR_RENDER_DYNAMIC_ATTR,\n  SSR_RENDER_STYLE,\n} from \"../runtimeHelpers\";\nimport { type SSRTransformContext, processChildren } from \"../ssrCodegenTransform\";\n\nexport const ssrTransformElement: NodeTransform = (node, context) => {\n  if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.ELEMENT) {\n    return;\n  }\n\n  return function ssrPostTransformElement() {\n    const openTag: TemplateLiteral[\"elements\"] = [`<${node.tag}`];\n\n    // process props\n    for (const prop of node.props) {\n      if (prop.type === NodeTypes.ATTRIBUTE) {\n        if (prop.name === \"key\" || prop.name === \"ref\") {\n          continue;\n        }\n        openTag.push(` ${prop.name}` + (prop.value ? `=\"${escapeHtml(prop.value.content)}\"` : ``));\n      } else if (prop.type === NodeTypes.DIRECTIVE) {\n        // handle v-bind\n        if (prop.name === \"bind\" && prop.arg && prop.exp) {\n          const attrName =\n            prop.arg.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic\n              ? prop.arg.content\n              : null;\n\n          if (attrName) {\n            if (attrName === \"key\" || attrName === \"ref\") {\n              continue;\n            }\n            if (attrName === \"class\") {\n              openTag.push(\n                ` class=\"`,\n                createCallExpression(context.helper(SSR_RENDER_CLASS), [prop.exp]),\n                `\"`,\n              );\n            } else if (attrName === \"style\") {\n              openTag.push(\n                ` style=\"`,\n                createCallExpression(context.helper(SSR_RENDER_STYLE), [prop.exp]),\n                `\"`,\n              );\n            } else {\n              const mappedName = propsToAttrMap[attrName] || attrName.toLowerCase();\n              if (isBooleanAttr(mappedName)) {\n                openTag.push(\n                  createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [prop.exp]),\n                );\n              } else if (isSSRSafeAttrName(mappedName)) {\n                openTag.push(\n                  createCallExpression(context.helper(SSR_RENDER_ATTR), [prop.arg, prop.exp]),\n                );\n              }\n            }\n          } else {\n            // dynamic attribute name\n            openTag.push(\n              createCallExpression(context.helper(SSR_RENDER_DYNAMIC_ATTR), [prop.arg!, prop.exp]),\n            );\n          }\n        }\n        // v-html\n        else if (prop.name === \"html\" && prop.exp) {\n          // handled in children\n        }\n        // v-text\n        else if (prop.name === \"text\" && prop.exp) {\n          // handled in children\n        }\n      }\n    }\n\n    node.ssrCodegenNode = createTemplateLiteral(openTag);\n  };\n};\n\nexport function ssrProcessElement(node: PlainElementNode, context: SSRTransformContext): void {\n  const elementsToAdd = node.ssrCodegenNode!.elements;\n  for (const element of elementsToAdd) {\n    context.pushStringPart(element);\n  }\n\n  // close open tag\n  context.pushStringPart(`>`);\n\n  // handle v-html\n  const vHtml = node.props.find((p) => p.type === NodeTypes.DIRECTIVE && p.name === \"html\");\n  if (vHtml && vHtml.type === NodeTypes.DIRECTIVE && vHtml.exp) {\n    context.pushStringPart(vHtml.exp);\n  } else if (node.children.length) {\n    // handle v-text\n    const vText = node.props.find((p) => p.type === NodeTypes.DIRECTIVE && p.name === \"text\");\n    if (vText && vText.type === NodeTypes.DIRECTIVE && vText.exp) {\n      context.pushStringPart(createCallExpression(context.helper(SSR_RENDER_ATTRS), [vText.exp]));\n    } else {\n      processChildren(node, context);\n    }\n  }\n\n  if (!isVoidTag(node.tag)) {\n    context.pushStringPart(`</${node.tag}>`);\n  }\n}\n"
  },
  {
    "path": "impl/compiler-ssr/src/transforms/ssrVFor.ts",
    "content": "import {\n  type ForNode,\n  type NodeTransform,\n  NodeTypes,\n  createCallExpression,\n  createForLoopParams,\n  createFunctionExpression,\n  createStructuralDirectiveTransform,\n  processFor,\n} from \"@chibivue/compiler-core\";\nimport { type SSRTransformContext, processChildrenAsStatement } from \"../ssrCodegenTransform\";\nimport { SSR_RENDER_LIST } from \"../runtimeHelpers\";\n\n// Plugin for the first transform pass, which simply constructs the AST node\nexport const ssrTransformFor: NodeTransform = createStructuralDirectiveTransform(\"for\", processFor);\n\n// This is called during the 2nd transform pass to construct the SSR-specific\n// codegen nodes.\nexport function ssrProcessFor(\n  node: ForNode,\n  context: SSRTransformContext,\n  disableNestedFragments = false,\n): void {\n  const needFragmentWrapper =\n    !disableNestedFragments &&\n    (node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT);\n  const renderLoop = createFunctionExpression(createForLoopParams(node.parseResult));\n  renderLoop.body = processChildrenAsStatement(node, context, needFragmentWrapper);\n  // v-for always renders a fragment unless explicitly disabled\n  if (!disableNestedFragments) {\n    context.pushStringPart(`<!--[-->`);\n  }\n  context.pushStatement(\n    createCallExpression(context.helper(SSR_RENDER_LIST), [node.source, renderLoop]),\n  );\n  if (!disableNestedFragments) {\n    context.pushStringPart(`<!--]-->`);\n  }\n}\n"
  },
  {
    "path": "impl/compiler-ssr/src/transforms/ssrVIf.ts",
    "content": "import {\n  type BlockStatement,\n  type IfBranchNode,\n  type IfNode,\n  type NodeTransform,\n  NodeTypes,\n  createBlockStatement,\n  createCallExpression,\n  createIfStatement,\n  createStructuralDirectiveTransform,\n  processIf,\n} from \"@chibivue/compiler-core\";\nimport { type SSRTransformContext, processChildrenAsStatement } from \"../ssrCodegenTransform\";\n\n// Plugin for the first transform pass, which simply constructs the AST node\nexport const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(\n  /^(?:if|else|else-if)$/,\n  processIf,\n);\n\n// This is called during the 2nd transform pass to construct the SSR-specific\n// codegen nodes.\nexport function ssrProcessIf(\n  node: IfNode,\n  context: SSRTransformContext,\n  disableNestedFragments = false,\n  disableComment = false,\n): void {\n  const [rootBranch] = node.branches;\n  const ifStatement = createIfStatement(\n    rootBranch.condition!,\n    processIfBranch(rootBranch, context, disableNestedFragments),\n  );\n  context.pushStatement(ifStatement);\n\n  let currentIf = ifStatement;\n  for (let i = 1; i < node.branches.length; i++) {\n    const branch = node.branches[i];\n    const branchBlockStatement = processIfBranch(branch, context, disableNestedFragments);\n    if (branch.condition) {\n      // else-if\n      currentIf = currentIf.alternate = createIfStatement(branch.condition, branchBlockStatement);\n    } else {\n      // else\n      currentIf.alternate = branchBlockStatement;\n    }\n  }\n\n  if (!currentIf.alternate && !disableComment) {\n    currentIf.alternate = createBlockStatement([createCallExpression(`_push`, [\"`<!---->`\"])]);\n  }\n}\n\nfunction processIfBranch(\n  branch: IfBranchNode,\n  context: SSRTransformContext,\n  disableNestedFragments = false,\n): BlockStatement {\n  const { children } = branch;\n  const needFragmentWrapper =\n    !disableNestedFragments &&\n    (children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&\n    // optimize away nested fragments when the only child is a ForNode\n    !(children.length === 1 && children[0].type === NodeTypes.FOR);\n  return processChildrenAsStatement(branch, context, needFragmentWrapper);\n}\n"
  },
  {
    "path": "impl/compiler-vapor/package.json",
    "content": "{\n  \"name\": \"@chibivue/compiler-vapor\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/compiler-vapor/src/codegen.ts",
    "content": "import type { SimpleExpressionNode, RootNode } from \"@chibivue/compiler-core\";\n\nimport type {\n  RootIRNode,\n  BlockIRNode,\n  OperationNode,\n  SetTextIRNode,\n  SetEventIRNode,\n  SetPropIRNode,\n  IfIRNode,\n  ForIRNode,\n} from \"./ir\";\nimport { IRNodeTypes } from \"./ir\";\n\nexport interface VaporCodegenResult {\n  code: string;\n  preamble: string;\n  ast: RootNode;\n}\n\nexport interface VaporCodegenOptions {\n  isBrowser?: boolean;\n  inline?: boolean;\n}\n\ninterface VaporCodegenContext {\n  code: string;\n  indentLevel: number;\n  push(code: string): void;\n  indent(): void;\n  deindent(): void;\n  newline(): void;\n}\n\nfunction createVaporCodegenContext(): VaporCodegenContext {\n  const context: VaporCodegenContext = {\n    code: \"\",\n    indentLevel: 0,\n    push(code: string) {\n      context.code += code;\n    },\n    indent() {\n      context.indentLevel++;\n      context.newline();\n    },\n    deindent() {\n      context.indentLevel--;\n      context.newline();\n    },\n    newline() {\n      context.push(\"\\n\" + \"  \".repeat(context.indentLevel));\n    },\n  };\n  return context;\n}\n\nexport function generateVaporFromIR(\n  ir: RootIRNode,\n  options: VaporCodegenOptions = {},\n): VaporCodegenResult {\n  const context = createVaporCodegenContext();\n  const { push, indent, deindent, newline } = context;\n  const isSetupInlined = !options.isBrowser && !!options.inline;\n\n  // Generate preamble\n  const preambleContext = isSetupInlined ? createVaporCodegenContext() : context;\n  genVaporPreamble(preambleContext, options.isBrowser);\n\n  // Generate component function\n  push(`((_self) => {`);\n  indent();\n\n  // Generate template call\n  const template = ir.template[0] || \"\";\n  push(`const _root = _template(\\`${template}\\`);`);\n  newline();\n\n  // Generate element references\n  const block = ir.block;\n  const elementCount = countElements(block);\n\n  for (let i = 0; i < elementCount; i++) {\n    push(`const _el${i} = _root${generateElementPath(i, elementCount)};`);\n    newline();\n  }\n\n  // Generate operations (non-reactive)\n  for (const op of block.operation) {\n    genOperation(op, context);\n  }\n\n  // Generate effects (reactive)\n  for (const effect of block.effect) {\n    push(`_renderEffect(() => {`);\n    indent();\n    for (const op of effect.operations) {\n      genOperation(op, context);\n    }\n    deindent();\n    push(`});`);\n    newline();\n  }\n\n  // Return root element\n  push(`return _root;`);\n  deindent();\n  push(`})`);\n\n  return {\n    code: context.code,\n    preamble: isSetupInlined ? preambleContext.code : \"\",\n    ast: ir.node,\n  };\n}\n\nfunction genVaporPreamble(context: VaporCodegenContext, isBrowser?: boolean) {\n  const { push, newline } = context;\n\n  if (isBrowser) {\n    push(\n      `const { template: _template, setText: _setText, on: _on, setClass: _setClass, setStyle: _setStyle, setAttr: _setAttr, renderEffect: _renderEffect } = ChibiVueVapor`,\n    );\n    newline();\n    push(`return `);\n  } else {\n    push(\n      `import { template as _template, setText as _setText, on as _on, setClass as _setClass, setStyle as _setStyle, setAttr as _setAttr, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"`,\n    );\n    newline();\n    newline();\n  }\n}\n\nfunction genOperation(op: OperationNode, context: VaporCodegenContext): void {\n  const { push, newline } = context;\n\n  switch (op.type) {\n    case IRNodeTypes.SET_TEXT:\n      genSetText(op, context);\n      break;\n    case IRNodeTypes.SET_EVENT:\n      genSetEvent(op, context);\n      break;\n    case IRNodeTypes.SET_PROP:\n      genSetProp(op, context);\n      break;\n    case IRNodeTypes.IF:\n      genIf(op, context);\n      break;\n    case IRNodeTypes.FOR:\n      genFor(op, context);\n      break;\n  }\n}\n\nfunction genSetText(op: SetTextIRNode, context: VaporCodegenContext): void {\n  const { push, newline } = context;\n  const values = op.values.map((v) => genExpression(v)).join(\", \");\n  push(`_setText(_el${op.element}, \"\", ${values});`);\n  newline();\n}\n\nfunction genSetEvent(op: SetEventIRNode, context: VaporCodegenContext): void {\n  const { push, newline } = context;\n  const modifiersArg = op.modifiers?.length ? `, ${JSON.stringify(op.modifiers)}` : \"\";\n  push(`_on(_el${op.element}, \"${op.key}\", ${genExpression(op.value)}${modifiersArg});`);\n  newline();\n}\n\nfunction genSetProp(op: SetPropIRNode, context: VaporCodegenContext): void {\n  const { push, newline } = context;\n  const key = op.key;\n  const value = genExpression(op.value);\n\n  if (key === \"class\") {\n    push(`_setClass(_el${op.element}, ${value});`);\n  } else if (key === \"style\") {\n    push(`_setStyle(_el${op.element}, ${value});`);\n  } else {\n    push(`_setAttr(_el${op.element}, \"${key}\", ${value});`);\n  }\n  newline();\n}\n\nfunction genIf(op: IfIRNode, context: VaporCodegenContext): void {\n  const { push, indent, deindent, newline } = context;\n  // TODO: Implement v-if code generation\n  push(`// TODO: v-if (id: ${op.id})`);\n  newline();\n}\n\nfunction genFor(op: ForIRNode, context: VaporCodegenContext): void {\n  const { push, newline } = context;\n  // TODO: Implement v-for code generation\n  push(`// TODO: v-for (id: ${op.id})`);\n  newline();\n}\n\nfunction genExpression(exp: SimpleExpressionNode): string {\n  return exp.content;\n}\n\nfunction countElements(block: BlockIRNode): number {\n  let count = block.returns.length;\n  // Also count elements referenced in operations and effects\n  for (const op of block.operation) {\n    if (\"element\" in op && typeof op.element === \"number\") {\n      count = Math.max(count, op.element + 1);\n    }\n  }\n  for (const effect of block.effect) {\n    for (const op of effect.operations) {\n      if (\"element\" in op && typeof op.element === \"number\") {\n        count = Math.max(count, op.element + 1);\n      }\n    }\n  }\n  return count;\n}\n\nfunction generateElementPath(index: number, total: number): string {\n  if (total <= 1) return \"\";\n  if (index === 0) return \".firstChild\";\n  return `.childNodes[${index}]`;\n}\n\n// Legacy function for backward compatibility\nexport function generateVapor(\n  ast: RootNode,\n  options: VaporCodegenOptions = {},\n): VaporCodegenResult {\n  const context = createVaporCodegenContext();\n  const { push, indent, deindent, newline } = context;\n  const isSetupInlined = !options.isBrowser && !!options.inline;\n\n  // Analyze template and collect info\n  const templateInfo = analyzeTemplate(ast);\n\n  // Generate preamble\n  const preambleContext = isSetupInlined ? createVaporCodegenContext() : context;\n  genVaporPreambleLegacy(preambleContext, options.isBrowser);\n\n  // Generate component function\n  push(`((_self) => {`);\n  indent();\n\n  // Generate template call\n  push(`const _root = _template(\\`${templateInfo.html}\\`);`);\n  newline();\n\n  // Generate element references\n  for (const ref of templateInfo.refs) {\n    push(`const ${ref.varName} = ${ref.path};`);\n    newline();\n  }\n\n  // Generate effects for dynamic bindings\n  for (const binding of templateInfo.bindings) {\n    if (binding.type === \"text\") {\n      push(`_renderEffect(() => {`);\n      indent();\n      push(`_setText(${binding.target}, \"\", ${binding.expression});`);\n      deindent();\n      push(`});`);\n      newline();\n    } else if (binding.type === \"event\") {\n      push(`_on(${binding.target}, \"${binding.event}\", ${binding.expression});`);\n      newline();\n    }\n  }\n\n  // Return root element\n  push(`return _root;`);\n  deindent();\n  push(`})`);\n\n  return {\n    code: context.code,\n    preamble: isSetupInlined ? preambleContext.code : \"\",\n    ast,\n  };\n}\n\nfunction genVaporPreambleLegacy(context: VaporCodegenContext, isBrowser?: boolean) {\n  const { push, newline } = context;\n\n  if (isBrowser) {\n    push(\n      `const { template: _template, setText: _setText, on: _on, renderEffect: _renderEffect } = ChibiVueVapor`,\n    );\n    newline();\n    push(`return `);\n  } else {\n    push(\n      `import { template as _template, setText as _setText, on as _on, renderEffect as _renderEffect } from \"@chibivue/runtime-vapor\"`,\n    );\n    newline();\n    newline();\n  }\n}\n\nimport {\n  NodeTypes,\n  type TextNode,\n  type ElementNode,\n  type InterpolationNode,\n  type DirectiveNode,\n  type TemplateChildNode,\n} from \"@chibivue/compiler-core\";\n\ninterface TemplateRef {\n  varName: string;\n  path: string;\n}\n\ninterface TemplateBinding {\n  type: \"text\" | \"event\";\n  target: string;\n  expression: string;\n  event?: string;\n}\n\ninterface TemplateAnalysis {\n  html: string;\n  refs: TemplateRef[];\n  bindings: TemplateBinding[];\n}\n\nfunction analyzeTemplate(ast: RootNode): TemplateAnalysis {\n  const refs: TemplateRef[] = [];\n  const bindings: TemplateBinding[] = [];\n  let refCounter = 0;\n\n  function generatePath(indices: number[]): string {\n    if (indices.length === 0) return \"_root\";\n    let path = \"_root\";\n    for (let i = 0; i < indices.length; i++) {\n      if (i === 0) {\n        path += indices[i] === 0 ? \".firstChild\" : `.childNodes[${indices[i]}]`;\n      } else {\n        path += indices[i] === 0 ? \".firstChild\" : `.childNodes[${indices[i]}]`;\n      }\n    }\n    return path;\n  }\n\n  function processNode(node: TemplateChildNode, parentPath: number[]): string {\n    if (node.type === NodeTypes.TEXT) {\n      return (node as TextNode).content;\n    }\n\n    if (node.type === NodeTypes.INTERPOLATION) {\n      const interp = node as InterpolationNode;\n      const varName = `_el${refCounter++}`;\n      const path = generatePath(parentPath);\n\n      refs.push({ varName, path });\n      bindings.push({\n        type: \"text\",\n        target: varName,\n        expression: (interp.content as SimpleExpressionNode).content,\n      });\n\n      return `<!---->`; // placeholder comment\n    }\n\n    if (node.type === NodeTypes.ELEMENT) {\n      const el = node as ElementNode;\n      let html = `<${el.tag}`;\n\n      // Process attributes and directives\n      for (const prop of el.props) {\n        if (prop.type === NodeTypes.ATTRIBUTE) {\n          html += ` ${prop.name}=\"${prop.value?.content || \"\"}\"`;\n        } else if (prop.type === NodeTypes.DIRECTIVE) {\n          const dir = prop as DirectiveNode;\n          if (dir.name === \"on\" && dir.arg && dir.exp) {\n            const varName = `_el${refCounter++}`;\n            const fullPath = generatePath(parentPath);\n            refs.push({ varName, path: fullPath });\n            bindings.push({\n              type: \"event\",\n              target: varName,\n              event: (dir.arg as SimpleExpressionNode).content,\n              expression: (dir.exp as SimpleExpressionNode).content,\n            });\n          }\n        }\n      }\n\n      html += `>`;\n\n      // Process children\n      for (let i = 0; i < el.children.length; i++) {\n        const childPath = [...parentPath, i];\n        html += processNode(el.children[i], childPath);\n      }\n\n      // Close tag (for non-void elements)\n      const voidElements = [\"br\", \"hr\", \"img\", \"input\", \"meta\", \"link\"];\n      if (!voidElements.includes(el.tag)) {\n        html += `</${el.tag}>`;\n      }\n\n      return html;\n    }\n\n    return \"\";\n  }\n\n  // Process all root children\n  let html = \"\";\n  for (let i = 0; i < ast.children.length; i++) {\n    html += processNode(ast.children[i], [i]);\n  }\n\n  return { html, refs, bindings };\n}\n"
  },
  {
    "path": "impl/compiler-vapor/src/compile.ts",
    "content": "import { baseParse } from \"@chibivue/compiler-core\";\nimport { parserOptions } from \"@chibivue/compiler-dom\";\n\nimport {\n  generateVapor,\n  generateVaporFromIR,\n  type VaporCodegenOptions,\n  type VaporCodegenResult,\n} from \"./codegen\";\nimport { transform } from \"./transform\";\n\nexport interface VaporCompilerOptions extends VaporCodegenOptions {\n  /**\n   * Use the new IR-based compilation pipeline.\n   * When true, uses: parse -> transform (IR) -> codegen\n   * When false, uses: parse -> codegen (legacy direct AST analysis)\n   */\n  useIR?: boolean;\n}\n\n/**\n * Compile a template string to Vapor render function code.\n *\n * Compilation Pipeline:\n * 1. Parse: template string -> AST (Abstract Syntax Tree)\n * 2. Transform: AST -> IR (Intermediate Representation)\n * 3. Codegen: IR -> JavaScript code\n *\n * The IR stage is crucial for optimization and allows:\n * - Separation of concerns between parsing and code generation\n * - Static analysis and optimization passes\n * - Easier maintenance and extensibility\n */\nexport function compile(template: string, options: VaporCompilerOptions = {}): VaporCodegenResult {\n  const ast = baseParse(template, parserOptions);\n\n  if (options.useIR) {\n    // New IR-based pipeline: AST -> IR -> Code\n    const ir = transform(ast, template);\n    return generateVaporFromIR(ir, options);\n  }\n\n  // Legacy pipeline: AST -> Code (direct analysis)\n  return generateVapor(ast, options);\n}\n\n// Re-export transform for advanced use cases\nexport { transform } from \"./transform\";\n"
  },
  {
    "path": "impl/compiler-vapor/src/index.ts",
    "content": "export { compile, transform, type VaporCompilerOptions } from \"./compile\";\nexport {\n  generateVapor,\n  generateVaporFromIR,\n  type VaporCodegenResult,\n  type VaporCodegenOptions,\n} from \"./codegen\";\nexport * from \"./runtimeHelpers\";\n\n// IR types and helpers\nexport {\n  IRNodeTypes,\n  DynamicFlag,\n  createBlock,\n  createRootIR,\n  type RootIRNode,\n  type BlockIRNode,\n  type IREffect,\n  type IRDynamicInfo,\n  type OperationNode,\n  type SetTextIRNode,\n  type SetEventIRNode,\n  type SetPropIRNode,\n  type InsertNodeIRNode,\n  type IfIRNode,\n  type ForIRNode,\n} from \"./ir\";\n\n// Transform types\nexport { type TransformContext } from \"./transform\";\n"
  },
  {
    "path": "impl/compiler-vapor/src/ir.ts",
    "content": "import type { SimpleExpressionNode, RootNode, TemplateChildNode } from \"@chibivue/compiler-core\";\n\nexport enum IRNodeTypes {\n  ROOT = \"root\",\n  BLOCK = \"block\",\n\n  // DOM Operations\n  SET_TEXT = \"setText\",\n  SET_EVENT = \"setEvent\",\n  SET_PROP = \"setProp\",\n\n  // Structure\n  INSERT_NODE = \"insertNode\",\n\n  // Control Flow\n  IF = \"if\",\n  FOR = \"for\",\n}\n\nexport enum DynamicFlag {\n  NONE = 0,\n  REFERENCED = 1,\n  NON_TEMPLATE = 2,\n  INSERT = 4,\n}\n\nexport interface IRDynamicInfo {\n  id?: number;\n  flags: DynamicFlag;\n}\n\nexport interface IREffect {\n  expressions: SimpleExpressionNode[];\n  operations: OperationNode[];\n}\n\n// Root IR Node\nexport interface RootIRNode {\n  type: IRNodeTypes.ROOT;\n  node: RootNode;\n  source: string;\n  template: string[];\n  block: BlockIRNode;\n}\n\n// Block IR Node - container for operations and effects\nexport interface BlockIRNode {\n  type: IRNodeTypes.BLOCK;\n  node: RootNode | TemplateChildNode;\n  dynamic: IRDynamicInfo;\n  effect: IREffect[];\n  operation: OperationNode[];\n  returns: number[];\n}\n\n// Operation Nodes\nexport interface SetTextIRNode {\n  type: IRNodeTypes.SET_TEXT;\n  element: number;\n  values: SimpleExpressionNode[];\n}\n\nexport interface SetEventIRNode {\n  type: IRNodeTypes.SET_EVENT;\n  element: number;\n  key: string;\n  value: SimpleExpressionNode;\n  modifiers?: string[];\n}\n\nexport interface SetPropIRNode {\n  type: IRNodeTypes.SET_PROP;\n  element: number;\n  key: string;\n  value: SimpleExpressionNode;\n}\n\nexport interface InsertNodeIRNode {\n  type: IRNodeTypes.INSERT_NODE;\n  element: number;\n  parent: number;\n  anchor?: number;\n}\n\nexport interface IfIRNode {\n  type: IRNodeTypes.IF;\n  id: number;\n  condition: SimpleExpressionNode;\n  positive: BlockIRNode;\n  negative?: BlockIRNode | IfIRNode;\n  once?: boolean;\n}\n\nexport interface ForIRNode {\n  type: IRNodeTypes.FOR;\n  id: number;\n  source: SimpleExpressionNode;\n  value?: SimpleExpressionNode;\n  key?: SimpleExpressionNode;\n  index?: SimpleExpressionNode;\n  render: BlockIRNode;\n  once?: boolean;\n}\n\nexport type OperationNode =\n  | SetTextIRNode\n  | SetEventIRNode\n  | SetPropIRNode\n  | InsertNodeIRNode\n  | IfIRNode\n  | ForIRNode;\n\n// Helper to create a new block\nexport function createBlock(node: RootNode | TemplateChildNode): BlockIRNode {\n  return {\n    type: IRNodeTypes.BLOCK,\n    node,\n    dynamic: { flags: DynamicFlag.NONE },\n    effect: [],\n    operation: [],\n    returns: [],\n  };\n}\n\n// Helper to create root IR\nexport function createRootIR(node: RootNode, source: string): RootIRNode {\n  return {\n    type: IRNodeTypes.ROOT,\n    node,\n    source,\n    template: [],\n    block: createBlock(node),\n  };\n}\n"
  },
  {
    "path": "impl/compiler-vapor/src/runtimeHelpers.ts",
    "content": "import { registerRuntimeHelpers } from \"@chibivue/compiler-core\";\n\nexport const V_TEMPLATE: unique symbol = Symbol(`template`);\nexport const V_SET_TEXT: unique symbol = Symbol(`setText`);\nexport const V_ON: unique symbol = Symbol(`on`);\nexport const V_CREATE_COMPONENT: unique symbol = Symbol(`createComponent`);\n\nexport const vaporHelperNameMap: Record<symbol, string> = {\n  [V_TEMPLATE]: \"template\",\n  [V_SET_TEXT]: \"setText\",\n  [V_ON]: \"on\",\n  [V_CREATE_COMPONENT]: \"createComponent\",\n};\n\nregisterRuntimeHelpers(vaporHelperNameMap);\n"
  },
  {
    "path": "impl/compiler-vapor/src/transform.ts",
    "content": "import {\n  type RootNode,\n  type TemplateChildNode,\n  type ElementNode,\n  type SimpleExpressionNode,\n  type DirectiveNode,\n  type InterpolationNode,\n  type TextNode,\n  NodeTypes,\n} from \"@chibivue/compiler-core\";\n\nimport {\n  type RootIRNode,\n  type BlockIRNode,\n  type OperationNode,\n  type IREffect,\n  IRNodeTypes,\n  DynamicFlag,\n  createRootIR,\n  createBlock,\n} from \"./ir\";\n\nexport interface TransformContext {\n  root: RootIRNode;\n  block: BlockIRNode;\n  template: string;\n  elementCount: number;\n  reference(): number;\n  registerEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void;\n  registerOperation(...operations: OperationNode[]): void;\n  enterBlock(block: BlockIRNode): () => void;\n}\n\nfunction createTransformContext(ir: RootIRNode): TransformContext {\n  let currentBlock = ir.block;\n  let elementCount = 0;\n\n  const context: TransformContext = {\n    root: ir,\n    get block() {\n      return currentBlock;\n    },\n    template: \"\",\n    elementCount: 0,\n\n    reference(): number {\n      return elementCount++;\n    },\n\n    registerEffect(expressions: SimpleExpressionNode[], operations: OperationNode[]): void {\n      // Filter out constant expressions\n      const reactiveExpressions = expressions.filter((exp) => !isConstantExpression(exp));\n\n      // If no reactive deps, register as operation\n      if (reactiveExpressions.length === 0) {\n        context.registerOperation(...operations);\n        return;\n      }\n\n      // Register as effect with dependencies\n      currentBlock.effect.push({\n        expressions: reactiveExpressions,\n        operations,\n      });\n    },\n\n    registerOperation(...operations: OperationNode[]): void {\n      currentBlock.operation.push(...operations);\n    },\n\n    enterBlock(block: BlockIRNode): () => void {\n      const parent = currentBlock;\n      currentBlock = block;\n      return () => {\n        currentBlock = parent;\n      };\n    },\n  };\n\n  return context;\n}\n\nfunction isConstantExpression(exp: SimpleExpressionNode): boolean {\n  return exp.isStatic;\n}\n\nexport function transform(ast: RootNode, source: string): RootIRNode {\n  const ir = createRootIR(ast, source);\n  const context = createTransformContext(ir);\n\n  // Transform children\n  transformChildren(ast.children, context);\n\n  // Store template\n  ir.template.push(context.template);\n\n  return ir;\n}\n\nfunction transformChildren(children: TemplateChildNode[], context: TransformContext): void {\n  for (const child of children) {\n    transformNode(child, context);\n  }\n}\n\nfunction transformNode(node: TemplateChildNode, context: TransformContext): void {\n  switch (node.type) {\n    case NodeTypes.ELEMENT:\n      transformElement(node as ElementNode, context);\n      break;\n    case NodeTypes.TEXT:\n      transformText(node as TextNode, context);\n      break;\n    case NodeTypes.INTERPOLATION:\n      transformInterpolation(node as InterpolationNode, context);\n      break;\n  }\n}\n\nfunction transformElement(node: ElementNode, context: TransformContext): void {\n  const elementId = context.reference();\n\n  // Start tag\n  context.template += `<${node.tag}`;\n\n  // Process props and directives\n  for (const prop of node.props) {\n    if (prop.type === NodeTypes.ATTRIBUTE) {\n      // Static attribute\n      context.template += ` ${prop.name}=\"${prop.value?.content || \"\"}\"`;\n    } else if (prop.type === NodeTypes.DIRECTIVE) {\n      transformDirective(prop, elementId, node, context);\n    }\n  }\n\n  context.template += `>`;\n\n  // Process children\n  transformChildren(node.children, context);\n\n  // Close tag (for non-void elements)\n  const voidElements = [\"br\", \"hr\", \"img\", \"input\", \"meta\", \"link\", \"area\", \"base\", \"col\"];\n  if (!voidElements.includes(node.tag)) {\n    context.template += `</${node.tag}>`;\n  }\n\n  // Mark for return\n  context.block.returns.push(elementId);\n}\n\nfunction transformDirective(\n  dir: DirectiveNode,\n  elementId: number,\n  node: ElementNode,\n  context: TransformContext,\n): void {\n  switch (dir.name) {\n    case \"on\":\n      transformVOn(dir, elementId, context);\n      break;\n    case \"bind\":\n      transformVBind(dir, elementId, context);\n      break;\n  }\n}\n\nfunction transformVOn(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const eventName = (dir.arg as SimpleExpressionNode).content;\n\n  // Events are always registered as operations (not effects)\n  // because the handler itself doesn't need reactive tracking\n  context.registerOperation({\n    type: IRNodeTypes.SET_EVENT,\n    element: elementId,\n    key: eventName,\n    value: dir.exp as SimpleExpressionNode,\n    modifiers: dir.modifiers,\n  });\n}\n\nfunction transformVBind(dir: DirectiveNode, elementId: number, context: TransformContext): void {\n  if (!dir.arg || !dir.exp) return;\n\n  const propName = (dir.arg as SimpleExpressionNode).content;\n\n  // Register as effect for reactive props\n  context.registerEffect(\n    [dir.exp as SimpleExpressionNode],\n    [\n      {\n        type: IRNodeTypes.SET_PROP,\n        element: elementId,\n        key: propName,\n        value: dir.exp as SimpleExpressionNode,\n      },\n    ],\n  );\n}\n\nfunction transformText(node: TextNode, context: TransformContext): void {\n  context.template += node.content;\n}\n\nfunction transformInterpolation(node: InterpolationNode, context: TransformContext): void {\n  const elementId = context.reference();\n\n  // Add placeholder comment\n  context.template += `<!---->`;\n\n  // Register effect for text update\n  context.registerEffect(\n    [node.content as SimpleExpressionNode],\n    [\n      {\n        type: IRNodeTypes.SET_TEXT,\n        element: elementId,\n        values: [node.content as SimpleExpressionNode],\n      },\n    ],\n  );\n}\n"
  },
  {
    "path": "impl/reactivity/package.json",
    "content": "{\n  \"name\": \"@chibivue/reactivity\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/reactivity/src/baseHandler.ts",
    "content": "import { hasChanged, isObject } from \"@chibivue/shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, type Target, isReadonly, reactive, readonly, toRaw } from \"./reactive\";\n\nconst get = createGetter();\nconst shallowGet = createGetter(false, true);\nconst readonlyGet = createGetter(true);\n\nfunction createGetter(isReadonly = false, shallow = false) {\n  return function get(target: Target, key: string | symbol, receiver: object) {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.IS_SHALLOW) {\n      return shallow;\n    } else {\n      track(target, key);\n      const res = Reflect.get(target, key, receiver);\n      if (isObject(res)) {\n        return isReadonly ? readonly(res) : reactive(res);\n      }\n      return res;\n    }\n  };\n}\n\nconst set = createSetter();\nconst shallowSet = createSetter(true);\n\nfunction createSetter(shallow = false) {\n  return function set(\n    target: object,\n    key: string | symbol,\n    value: unknown,\n    receiver: object,\n  ): boolean {\n    if (isReadonly(target)) return false;\n\n    let oldValue = (target as any)[key];\n    if (!shallow) {\n      oldValue = toRaw(oldValue);\n      value = toRaw(value);\n    } else {\n    }\n\n    const result = Reflect.set(target, key, value, receiver);\n    if (hasChanged(value, oldValue)) {\n      trigger(target, key);\n    }\n\n    return result;\n  };\n}\n\nexport const mutableHandlers: ProxyHandler<object> = {\n  get,\n  set,\n  ownKeys(target) {\n    track(target, ITERATE_KEY);\n    return Reflect.ownKeys(target);\n  },\n};\n\nexport const readonlyHandlers: ProxyHandler<object> = {\n  get: readonlyGet,\n  set(_target, _key) {\n    return true;\n  },\n  deleteProperty(_target, _key) {\n    return true;\n  },\n};\n\nexport const shallowReactiveHandlers: ProxyHandler<object> = Object.assign({}, mutableHandlers, {\n  get: shallowGet,\n  set: shallowSet,\n});\n"
  },
  {
    "path": "impl/reactivity/src/collectionHandlers.ts",
    "content": "import { hasChanged, hasOwn, isMap } from \"@chibivue/shared\";\nimport { ITERATE_KEY, track, trigger } from \"./effect\";\nimport { ReactiveFlags, toRaw, toReactive, toReadonly } from \"./reactive\";\n\nexport type CollectionTypes = IterableCollections | WeakCollections;\n\ntype IterableCollections = Map<any, any> | Set<any>;\ntype WeakCollections = WeakMap<any, any> | WeakSet<any>;\ntype MapTypes = Map<any, any> | WeakMap<any, any>;\ntype SetTypes = Set<any> | WeakSet<any>;\n\nconst toShallow = <T extends unknown>(value: T): T => value;\n\nconst getProto = <T extends CollectionTypes>(v: T): any => Reflect.getPrototypeOf(v);\n\nfunction get(target: MapTypes, key: unknown, isReadonly = false, isShallow = false) {\n  target = (target as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n\n  const { has } = getProto(rawTarget);\n  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n  if (has.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\n\nfunction has(this: CollectionTypes, key: unknown): boolean {\n  const target = (this as any)[ReactiveFlags.RAW];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  track(rawTarget, rawKey);\n  return target.has(key) || target.has(rawKey);\n}\n\nfunction size(target: IterableCollections) {\n  target = (target as any)[ReactiveFlags.RAW];\n  track(target, ITERATE_KEY);\n  return Reflect.get(toRaw(target), \"size\", target);\n}\n\nfunction add(this: SetTypes, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction set(this: MapTypes, key: unknown, value: unknown) {\n  value = toRaw(value);\n  const target = toRaw(this);\n\n  const { has, get } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n\n  const oldValue = get.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, ITERATE_KEY);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, ITERATE_KEY);\n  }\n  return this;\n}\n\nfunction deleteEntry(this: CollectionTypes, key: unknown) {\n  const target = toRaw(this);\n  const { has } = getProto(target);\n  let hadKey = has.call(target, key);\n  if (!hadKey) hadKey = has.call(target, key);\n  const result = target.delete(key);\n  if (hadKey) trigger(target, key);\n  return result;\n}\n\nfunction clear(this: IterableCollections) {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const result = target.clear();\n  if (hadItems) trigger(target);\n  return result;\n}\n\nfunction createForEach(isReadonly: boolean, isShallow: boolean) {\n  return function forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {\n    const observed = this as any;\n    const target = observed[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;\n    !isReadonly && track(rawTarget, ITERATE_KEY);\n    return target.forEach((value: unknown, key: unknown) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\n\ninterface Iterable {\n  [Symbol.iterator](): Iterator;\n}\n\ninterface Iterator {\n  next(value?: any): IterationResult;\n}\n\ninterface IterationResult {\n  value: any;\n  done: boolean;\n}\n\nfunction createIterableMethod(method: string | symbol) {\n  return function (this: IterableCollections, ...args: unknown[]): Iterable & Iterator {\n    const target = (this as any)[ReactiveFlags.RAW];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || (method === Symbol.iterator && targetIsMap);\n    const innerIterator = target[method](...args);\n    track(rawTarget, ITERATE_KEY);\n    return {\n      next() {\n        const { value, done } = innerIterator.next();\n        return done\n          ? { value, done }\n          : {\n              value: isPair ? [toReactive(value[0]), toReactive(value[1])] : toReactive(value),\n              done,\n            };\n      },\n      [Symbol.iterator]() {\n        return this;\n      },\n    };\n  };\n}\n\nfunction createReadonlyMethod(): Function {\n  return function (this: CollectionTypes, ...args: unknown[]) {\n    return this;\n  };\n}\n\nfunction createInstrumentations() {\n  const mutableInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false),\n  };\n\n  const shallowInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true),\n  };\n\n  const readonlyInstrumentations: Record<string, Function | number> = {\n    get(this: MapTypes, key: unknown) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this as unknown as IterableCollections);\n    },\n    has(this: MapTypes, key: unknown) {\n      return has.call(this, key);\n    },\n    add: createReadonlyMethod(),\n    set: createReadonlyMethod(),\n    delete: createReadonlyMethod(),\n    clear: createReadonlyMethod(),\n    forEach: createForEach(true, false),\n  };\n\n  const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations[method as string] = createIterableMethod(method);\n    readonlyInstrumentations[method as string] = createIterableMethod(method);\n    shallowInstrumentations[method as string] = createIterableMethod(method);\n  });\n\n  return [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations];\n}\n\nconst [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations] =\n  createInstrumentations();\n\nfunction createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {\n  return (target: CollectionTypes, key: string | symbol, receiver: CollectionTypes) => {\n    if (key === ReactiveFlags.IS_REACTIVE) {\n      return !isReadonly;\n    } else if (key === ReactiveFlags.IS_READONLY) {\n      return isReadonly;\n    } else if (key === ReactiveFlags.RAW) {\n      return target;\n    }\n\n    return Reflect.get(\n      hasOwn(\n        shallow\n          ? shallowInstrumentations\n          : isReadonly\n            ? readonlyInstrumentations\n            : mutableInstrumentations,\n        key,\n      ) && key in target\n        ? isReadonly\n          ? readonlyInstrumentations\n          : mutableInstrumentations\n        : target,\n      key,\n      receiver,\n    );\n  };\n}\n\nexport const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(false, false),\n};\n\nexport const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, false),\n};\n\nexport const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {\n  get: createInstrumentationGetter(true, true),\n};\n"
  },
  {
    "path": "impl/reactivity/src/computed.ts",
    "content": "import { isFunction } from \"@chibivue/shared\";\nimport type { Dep } from \"./dep\";\nimport { ReactiveEffect } from \"./effect\";\nimport { type Ref, trackRefValue, triggerRefValue } from \"./ref\";\n\nexport interface ComputedRef<T = any> extends WritableComputedRef<T> {\n  readonly value: T;\n}\n\ninterface WritableComputedRef<T> extends Ref<T> {\n  readonly effect: ReactiveEffect<T>;\n}\n\nexport type ComputedGetter<T> = (...args: any[]) => T;\nexport type ComputedSetter<T> = (v: T) => void;\nexport interface WritableComputedOptions<T> {\n  get: ComputedGetter<T>;\n  set: ComputedSetter<T>;\n}\n\nexport class ComputedRefImpl<T> {\n  public dep?: Dep = undefined;\n  private _value!: T;\n  public readonly effect: ReactiveEffect<T>;\n  public readonly __v_isRef = true;\n\n  public _dirty = true;\n\n  constructor(\n    getter: ComputedGetter<T>,\n    private readonly _setter: ComputedSetter<T>,\n  ) {\n    this.effect = new ReactiveEffect(getter, () => {\n      if (!this._dirty) {\n        this._dirty = true;\n        triggerRefValue(this);\n      }\n    });\n    this.effect.computed = this;\n  }\n\n  get value() {\n    trackRefValue(this);\n    if (this._dirty) {\n      this._dirty = false;\n      this._value = this.effect.run()!;\n    }\n    return this._value;\n  }\n\n  set value(newValue: T) {\n    this._setter(newValue);\n  }\n}\n\nexport function computed<T>(getterOrOptions: ComputedGetter<T>): ComputedRef<T>;\nexport function computed<T>(getterOrOptions: WritableComputedOptions<T>): Ref<T>;\nexport function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {\n  let getter: ComputedGetter<T>;\n  let setter: ComputedSetter<T>;\n\n  const onlyGetter = isFunction(getterOrOptions);\n\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {};\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  return new ComputedRefImpl(getter, setter) as any;\n}\n"
  },
  {
    "path": "impl/reactivity/src/dep.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nexport type Dep = Set<ReactiveEffect>;\n\nexport const createDep = (effects?: ReactiveEffect[]): Dep => {\n  const dep: Dep = new Set<ReactiveEffect>(effects);\n  return dep;\n};\n"
  },
  {
    "path": "impl/reactivity/src/effect.ts",
    "content": "import { isArray, isIntegerKey } from \"@chibivue/shared\";\nimport type { ComputedRefImpl } from \"./computed\";\nimport { type Dep, createDep } from \"./dep\";\nimport { type EffectScope, recordEffectScope } from \"./effectScope\";\n\ntype KeyToDepMap = Map<any, Dep>;\nconst targetMap = new WeakMap<any, KeyToDepMap>();\n\nexport let activeEffect: ReactiveEffect | undefined;\n\nexport type EffectScheduler = (...args: any[]) => any;\n\nexport const ITERATE_KEY: unique symbol = Symbol();\n\nexport class ReactiveEffect<T = any> {\n  active = true;\n  parent: ReactiveEffect | undefined = undefined;\n  computed?: ComputedRefImpl<T>;\n\n  private deferStop?: boolean;\n  onStop?: () => void;\n\n  constructor(\n    public fn: () => T,\n    public scheduler: EffectScheduler | null = null,\n    scope?: EffectScope,\n  ) {\n    recordEffectScope(this, scope);\n  }\n\n  run(): T | undefined {\n    if (!this.active) {\n      return this.fn();\n    }\n\n    try {\n      this.parent = activeEffect;\n      activeEffect = this;\n      const res = this.fn();\n      return res;\n    } finally {\n      activeEffect = this.parent;\n      this.parent = undefined;\n      if (this.deferStop) {\n        this.stop();\n      }\n    }\n  }\n\n  stop(): void {\n    if (activeEffect === this) {\n      this.deferStop = true;\n    } else if (this.active) {\n      if (this.onStop) {\n        this.onStop();\n      }\n      this.active = false;\n    }\n  }\n}\n\nexport function track(target: object, key: unknown): void {\n  let depsMap = targetMap.get(target);\n  if (!depsMap) {\n    targetMap.set(target, (depsMap = new Map()));\n  }\n\n  let dep = depsMap.get(key);\n  if (!dep) {\n    depsMap.set(key, (dep = createDep()));\n  }\n\n  trackEffects(dep);\n}\n\nexport function trackEffects(dep: Dep): void {\n  if (activeEffect) {\n    dep.add(activeEffect);\n  }\n}\n\nexport function trigger(target: object, key?: unknown): void {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) return;\n\n  let deps: (Dep | undefined)[] = [];\n  if (key !== void 0) {\n    deps.push(depsMap.get(key));\n  }\n\n  if (!isArray(target)) {\n    deps.push(depsMap.get(ITERATE_KEY));\n  } else if (isIntegerKey(key)) {\n    deps.push(depsMap.get(\"length\"));\n  }\n\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(dep);\n    }\n  }\n}\n\nexport function triggerEffects(dep: Dep | ReactiveEffect[]): void {\n  const effects = isArray(dep) ? dep : [...dep];\n  for (const effect of effects) {\n    if (effect.computed) {\n      triggerEffect(effect);\n    }\n  }\n\n  for (const effect of effects) {\n    if (!effect.computed) {\n      triggerEffect(effect);\n    }\n  }\n}\n\nfunction triggerEffect(effect: ReactiveEffect) {\n  if (effect.scheduler) {\n    effect.scheduler();\n  } else {\n    effect.run();\n  }\n}\n\nexport function getDepFromReactive(object: any, key: string | number | symbol): Dep | undefined {\n  return targetMap.get(object)?.get(key);\n}\n\nexport interface ReactiveEffectRunner<T = any> {\n  (): T;\n  effect: ReactiveEffect;\n}\n\nexport function effect<T = any>(fn: () => T): ReactiveEffectRunner {\n  if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {\n    fn = (fn as ReactiveEffectRunner).effect.fn;\n  }\n\n  const _effect = new ReactiveEffect(fn);\n  _effect.run();\n\n  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner;\n  runner.effect = _effect;\n  return runner;\n}\n"
  },
  {
    "path": "impl/reactivity/src/effectScope.ts",
    "content": "import type { ReactiveEffect } from \"./effect\";\n\nlet activeEffectScope: EffectScope | undefined;\n\nexport class EffectScope {\n  private _active = true;\n\n  effects: ReactiveEffect[] = [];\n  cleanups: (() => void)[] = [];\n\n  parent: EffectScope | undefined;\n  scopes: EffectScope[] | undefined;\n\n  constructor() {\n    this.parent = activeEffectScope;\n  }\n\n  get active(): boolean {\n    return this._active;\n  }\n\n  run<T>(fn: () => T): T | undefined {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    }\n  }\n\n  on(): void {\n    activeEffectScope = this;\n  }\n\n  off(): void {\n    activeEffectScope = this.parent;\n  }\n\n  stop(): void {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop();\n        }\n      }\n      this.parent = undefined;\n      this._active = false;\n    }\n  }\n}\n\nexport function effectScope(): EffectScope {\n  return new EffectScope();\n}\n\nexport function recordEffectScope(\n  effect: ReactiveEffect,\n  scope: EffectScope | undefined = activeEffectScope,\n): void {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\n\nexport function getCurrentScope(): EffectScope | undefined {\n  return activeEffectScope;\n}\n\nexport function onScopeDispose(fn: () => void): void {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  }\n}\n"
  },
  {
    "path": "impl/reactivity/src/index.ts",
    "content": "export {\n  reactive,\n  readonly,\n  shallowReactive,\n  isReactive,\n  isReadonly,\n  isProxy,\n  toRaw,\n  markRaw,\n  toReactive,\n  toReadonly,\n  ReactiveFlags,\n  type ShallowReactive,\n  type DeepReadonly,\n  type UnwrapNestedRefs,\n} from \"./reactive\";\nexport {\n  ref,\n  shallowRef,\n  isRef,\n  unref,\n  proxyRefs,\n  customRef,\n  triggerRef,\n  toRef,\n  toRefs,\n  type Ref,\n  type ShallowRef,\n  type MaybeRef,\n  type MaybeRefOrGetter,\n  type ToRef,\n  type ToRefs,\n  type ShallowUnwrapRef,\n  type UnwrapRef,\n  type CustomRefFactory,\n} from \"./ref\";\nexport * from \"./computed\";\nexport * from \"./effect\";\nexport {\n  EffectScope,\n  effectScope,\n  recordEffectScope,\n  getCurrentScope,\n  onScopeDispose,\n} from \"./effectScope\";\n"
  },
  {
    "path": "impl/reactivity/src/reactive.ts",
    "content": "import { isObject, toRawType } from \"@chibivue/shared\";\nimport { mutableHandlers, readonlyHandlers, shallowReactiveHandlers } from \"./baseHandler\";\nimport { mutableCollectionHandlers, readonlyCollectionHandlers } from \"./collectionHandlers\";\nimport type { Ref, UnwrapRefSimple } from \"./ref\";\n\nexport const enum ReactiveFlags {\n  IS_REACTIVE = \"__v_isReactive\",\n  IS_READONLY = \"__v_isReadonly\",\n  IS_SHALLOW = \"__v_isShallow\",\n  RAW = \"__v_raw\",\n}\n\nexport interface Target {\n  [ReactiveFlags.IS_REACTIVE]?: boolean;\n  [ReactiveFlags.IS_READONLY]?: boolean;\n  [ReactiveFlags.IS_SHALLOW]?: boolean;\n  [ReactiveFlags.RAW]?: any;\n}\n\nconst enum TargetType {\n  INVALID = 0,\n  COMMON = 1,\n  COLLECTION = 2,\n}\n\nfunction targetTypeMap(rawType: string) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return TargetType.COMMON;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return TargetType.COLLECTION;\n    default:\n      return TargetType.INVALID;\n  }\n}\n\nfunction getTargetType<T extends object>(value: T) {\n  return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));\n}\n\nexport type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;\n\nexport function reactive<T extends object>(target: T): T {\n  return createReactiveObject(target, mutableHandlers, mutableCollectionHandlers);\n}\n\nexport declare const ShallowReactiveMarker: unique symbol;\nexport type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true };\nexport function shallowReactive<T extends object>(target: T): ShallowReactive<T> {\n  return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveHandlers);\n}\n\ntype Primitive = string | number | boolean | bigint | symbol | undefined | null;\ntype Builtin = Primitive | Function | Date | Error | RegExp;\nexport type DeepReadonly<T> = T extends Builtin\n  ? T\n  : T extends Map<infer K, infer V>\n    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n    : T extends ReadonlyMap<infer K, infer V>\n      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n      : T extends WeakMap<infer K, infer V>\n        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>\n        : T extends Set<infer U>\n          ? ReadonlySet<DeepReadonly<U>>\n          : T extends ReadonlySet<infer U>\n            ? ReadonlySet<DeepReadonly<U>>\n            : T extends WeakSet<infer U>\n              ? WeakSet<DeepReadonly<U>>\n              : T extends Promise<infer U>\n                ? Promise<DeepReadonly<U>>\n                : T extends Ref<infer U>\n                  ? Readonly<Ref<DeepReadonly<U>>>\n                  : T extends {}\n                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n                    : Readonly<T>;\n\nexport function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {\n  return createReactiveObject(target, readonlyHandlers, readonlyCollectionHandlers);\n}\n\nfunction createReactiveObject<T extends object>(\n  target: T,\n  baseHandlers: ProxyHandler<any>,\n  collectionHandlers: ProxyHandler<any>,\n) {\n  const targetType = getTargetType(target);\n  if (targetType === TargetType.INVALID) {\n    return target;\n  }\n\n  const proxy = new Proxy(\n    target,\n    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,\n  );\n  return proxy;\n}\n\nexport function isReadonly(value: unknown): boolean {\n  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);\n}\n\nexport function isReactive(value: unknown): boolean {\n  if (isReadonly(value)) {\n    return isReactive((value as Target)[ReactiveFlags.RAW]);\n  }\n  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]);\n}\n\nexport function isProxy(value: unknown): boolean {\n  return isReactive(value) || isReadonly(value);\n}\n\nexport const toReactive = <T extends unknown>(value: T): T =>\n  isObject(value) ? reactive(value) : value;\n\nexport const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>\n  isObject(value) ? readonly(value) : (value as DeepReadonly<T>);\n\nexport function toRaw<T>(observed: T): T {\n  const raw = observed && (observed as Target)[ReactiveFlags.RAW];\n  return raw ? toRaw(raw) : observed;\n}\n\nexport const enum ReactiveMarkerFlags {\n  SKIP = \"__v_skip\",\n}\n\nexport function markRaw<T extends object>(value: T): T {\n  Object.defineProperty(value, ReactiveMarkerFlags.SKIP, {\n    configurable: true,\n    enumerable: false,\n    value: true,\n  });\n  return value;\n}\n"
  },
  {
    "path": "impl/reactivity/src/ref.ts",
    "content": "import { type IfAny, isArray } from \"@chibivue/shared\";\nimport type { CollectionTypes } from \"./collectionHandlers\";\nimport { type Dep, createDep } from \"./dep\";\nimport { getDepFromReactive, trackEffects, triggerEffects } from \"./effect\";\nimport { type ShallowReactiveMarker, isReactive, toReactive } from \"./reactive\";\n\ndeclare const RefSymbol: unique symbol;\nexport declare const RawSymbol: unique symbol;\n\ntype RefBase<T> = {\n  dep?: Dep;\n  value: T;\n};\n\nexport interface Ref<T = any> {\n  value: T;\n  [RefSymbol]: true;\n}\n\nexport function trackRefValue(ref: RefBase<any>): void {\n  trackEffects(ref.dep || (ref.dep = createDep()));\n}\n\nexport function triggerRefValue(ref: RefBase<any>): void {\n  if (ref.dep) triggerEffects(ref.dep);\n}\n\nexport function isRef<T>(r: Ref<T> | unknown): r is Ref<T>;\nexport function isRef(r: any): r is Ref {\n  return !!(r && r.__v_isRef === true);\n}\n\n/*\n *\n * ref\n *\n */\nexport function ref<T = any>(): Ref<T | undefined>;\nexport function ref<T = any>(value: T): Ref<T>;\nexport function ref(value?: unknown) {\n  return createRef(value, false);\n}\n\n/*\n *\n * shallow ref\n *\n */\ndeclare const ShallowRefMarker: unique symbol;\nexport type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true };\n\nexport function shallowRef<T extends object>(value: T): T extends Ref ? T : ShallowRef<T>;\nexport function shallowRef<T>(value: T): ShallowRef<T>;\nexport function shallowRef<T = any>(): ShallowRef<T | undefined>;\nexport function shallowRef(value?: unknown) {\n  return createRef(value, true);\n}\n\n/*\n *\n * ref common\n *\n */\nfunction createRef(rawValue: unknown, shallow: boolean) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\n\nclass RefImpl<T> {\n  private _value: T;\n  public dep?: Dep = undefined;\n  public readonly __v_isRef = true;\n\n  constructor(\n    value: T,\n    public readonly __v_isShallow: boolean,\n  ) {\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n\n  set value(newVal) {\n    this._value = this.__v_isShallow ? newVal : toReactive(newVal);\n    triggerRefValue(this);\n  }\n}\n\nexport function triggerRef(ref: Ref): void {\n  triggerRefValue(ref);\n}\n\nexport type MaybeRef<T = any> = T | Ref<T>;\nexport type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T);\nexport function unref<T>(ref: MaybeRef<T>): T {\n  return isRef(ref) ? ref.value : ref;\n}\n\nconst shallowUnwrapHandlers: ProxyHandler<any> = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  },\n};\n\nexport function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {\n  return (\n    isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers)\n  ) as ShallowUnwrapRef<T>;\n}\n\nexport type ShallowUnwrapRef<T> = {\n  [K in keyof T]: T[K] extends Ref<infer V>\n    ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined\n    : T[K] extends Ref<infer V> | undefined\n      ? unknown extends V\n        ? undefined\n        : V | undefined\n      : T[K];\n};\n\n/*\n *\n * custom ref\n *\n */\nexport type CustomRefFactory<T> = (\n  track: () => void,\n  trigger: () => void,\n) => {\n  get: () => T;\n  set: (value: T) => void;\n};\n\nclass CustomRefImpl<T> {\n  public dep?: Dep = undefined;\n  private readonly _get: ReturnType<CustomRefFactory<T>>[\"get\"];\n  private readonly _set: ReturnType<CustomRefFactory<T>>[\"set\"];\n  public readonly __v_isRef = true;\n\n  constructor(factory: CustomRefFactory<T>) {\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this),\n    );\n    this._get = get;\n    this._set = set;\n  }\n\n  get value() {\n    return this._get();\n  }\n\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\n\nexport function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {\n  return new CustomRefImpl(factory) as any;\n}\n/*\n *\n * toRef\n *\n */\nexport type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;\nexport function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;\nexport function toRef<T extends object, K extends keyof T>(\n  object: T,\n  key: K,\n  defaultValue: T[K],\n): ToRef<Exclude<T[K], undefined>>;\nexport function toRef(source: Record<string, any>, key?: string, defaultValue?: unknown): Ref {\n  return propertyToRef(source, key!, defaultValue);\n}\n\n/*\n *\n * toRefs\n *\n */\nexport type ToRefs<T = any> = {\n  [K in keyof T]: ToRef<T[K]>;\n};\nexport function toRefs<T extends object>(object: T): ToRefs<T> {\n  const ret: any = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\n\n/*\n *\n * common (to ref)\n *\n */\nfunction propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {\n  return new ObjectRefImpl(source, key, defaultValue) as any;\n}\n\nclass ObjectRefImpl<T extends object, K extends keyof T> {\n  public readonly __v_isRef = true;\n\n  constructor(\n    private readonly _object: T,\n    private readonly _key: K,\n    private readonly _defaultValue?: T[K],\n  ) {}\n\n  get value() {\n    const val = this._object[this._key];\n    return val === undefined ? (this._defaultValue as T[K]) : val;\n  }\n\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n\n  get dep(): Dep | undefined {\n    return getDepFromReactive(this._object, this._key);\n  }\n}\n\ntype BaseTypes = string | number | boolean;\nexport interface RefUnwrapBailTypes {}\n\nexport type UnwrapRef<T> =\n  T extends ShallowRef<infer V>\n    ? V\n    : T extends Ref<infer V>\n      ? UnwrapRefSimple<V>\n      : UnwrapRefSimple<T>;\n\nexport type UnwrapRefSimple<T> = T extends\n  | Function\n  | CollectionTypes\n  | BaseTypes\n  | Ref\n  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]\n  | { [RawSymbol]?: true }\n  ? T\n  : T extends ReadonlyArray<any>\n    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }\n    : T extends object & { [ShallowReactiveMarker]?: never }\n      ? {\n          [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;\n        }\n      : T;\n"
  },
  {
    "path": "impl/runtime-core/package.json",
    "content": "{\n  \"name\": \"@chibivue/runtime-core\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/runtime-core/src/apiAsyncComponent.ts",
    "content": "import { ref } from \"@chibivue/reactivity\";\nimport { isFunction, isObject } from \"@chibivue/shared\";\nimport type { Component, ComponentInternalInstance } from \"./component\";\nimport { currentInstance } from \"./component\";\nimport type { VNode } from \"./vnode\";\nimport { createVNode } from \"./vnode\";\n\nexport type AsyncComponentResolveResult<T = Component> = T | { default: T };\n\nexport type AsyncComponentLoader<T = any> = () => Promise<AsyncComponentResolveResult<T>>;\n\nexport interface AsyncComponentOptions<T = any> {\n  loader: AsyncComponentLoader<T>;\n  loadingComponent?: Component;\n  errorComponent?: Component;\n  delay?: number;\n  timeout?: number;\n  onError?: (error: Error, retry: () => void, fail: () => void, attempts: number) => any;\n}\n\nexport function defineAsyncComponent<T extends Component = { new (): any }>(\n  source: AsyncComponentLoader<T> | AsyncComponentOptions<T>,\n): T {\n  if (isFunction(source)) {\n    source = { loader: source };\n  }\n\n  const {\n    loader,\n    loadingComponent,\n    errorComponent,\n    delay = 200,\n    timeout,\n    onError: userOnError,\n  } = source;\n\n  let pendingRequest: Promise<Component> | null = null;\n  let resolvedComp: Component | undefined;\n\n  let retries = 0;\n  const retry = (): Promise<Component> => {\n    retries++;\n    pendingRequest = null;\n    return load();\n  };\n\n  const load = (): Promise<Component> => {\n    let thisRequest: Promise<Component>;\n    return (\n      pendingRequest ||\n      (thisRequest = pendingRequest =\n        loader()\n          .catch((err: Error) => {\n            err = err instanceof Error ? err : new Error(String(err));\n            if (userOnError) {\n              return new Promise((resolve, reject) => {\n                const userRetry = () => resolve(retry());\n                const userFail = () => reject(err);\n                userOnError(err, userRetry, userFail, retries + 1);\n              });\n            } else {\n              throw err;\n            }\n          })\n          .then((comp) => {\n            if (thisRequest !== pendingRequest && pendingRequest) {\n              return pendingRequest;\n            }\n            if (comp && (comp as any).__esModule) {\n              comp = (comp as any).default;\n            }\n            if (comp && !isObject(comp) && !isFunction(comp)) {\n              throw new Error(`Invalid async component load result: ${comp}`);\n            }\n            resolvedComp = comp as Component;\n            return comp as Component;\n          }))\n    );\n  };\n\n  return {\n    name: \"AsyncComponentWrapper\",\n    __asyncLoader: load,\n    get __asyncResolved(): Component | undefined {\n      return resolvedComp;\n    },\n    setup() {\n      const instance = currentInstance as ComponentInternalInstance;\n\n      // already resolved\n      if (resolvedComp) {\n        return () => createInnerComp(resolvedComp!, instance);\n      }\n\n      const onError = (err: Error): void => {\n        pendingRequest = null;\n        error.value = err;\n      };\n\n      const loaded = ref(false);\n      const error = ref<Error | undefined>();\n      const delayed = ref(!!delay);\n\n      if (delay) {\n        setTimeout(() => {\n          delayed.value = false;\n        }, delay);\n      }\n\n      if (timeout != null) {\n        setTimeout(() => {\n          if (!loaded.value && !error.value) {\n            const err = new Error(`Async component timed out after ${timeout}ms.`);\n            onError(err);\n          }\n        }, timeout);\n      }\n\n      load()\n        .then(() => {\n          loaded.value = true;\n        })\n        .catch((err) => {\n          onError(err);\n        });\n\n      return (): VNode | null => {\n        if (loaded.value && resolvedComp) {\n          return createInnerComp(resolvedComp, instance);\n        } else if (error.value && errorComponent) {\n          return createVNode(errorComponent, { error: error.value });\n        } else if (loadingComponent && !delayed.value) {\n          return createVNode(loadingComponent);\n        }\n        return null;\n      };\n    },\n  } as any;\n}\n\nfunction createInnerComp(comp: Component, parent: ComponentInternalInstance): VNode {\n  const { props, children } = parent.vnode;\n  const vnode = createVNode(comp, props, children);\n  return vnode;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/apiComputed.ts",
    "content": "import { computed as _computed } from \"@chibivue/reactivity\";\n\nexport const computed = ((getter: any) => {\n  return _computed(getter);\n}) as typeof _computed;\n"
  },
  {
    "path": "impl/runtime-core/src/apiCreateApp.ts",
    "content": "import { createVNode } from \"./vnode\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { RootRenderFunction } from \"./renderer\";\nimport type { Component } from \"./component\";\nimport type { InjectionKey } from \"./apiInject\";\n\nexport interface AppConfig {\n  globalProperties: Record<string, any>;\n}\n\nexport interface App<HostElement = any> {\n  use(plugin: Plugin, ...options: any[]): App;\n  component(name: string, component: Component): this;\n  mount(rootContainer: HostElement | string): void;\n  provide<T>(key: InjectionKey<T> | string, value: T): this;\n  config: AppConfig;\n  /** @internal */\n  _component: Component;\n  /** @internal */\n  _props: Record<string, unknown> | null;\n  /** @internal */\n  _context: AppContext;\n}\n\nexport type CreateAppFunction<HostElement> = (rootComponent: Component) => App<HostElement>;\n\nexport interface AppContext {\n  app: App; // for devtools\n  provides: Record<string | symbol, any>;\n  components: Record<string, Component>;\n}\n\nexport type Plugin = {\n  install: (app: App, ...options: any[]) => any;\n};\n\nexport function createAppContext(): AppContext {\n  return {\n    app: null as any,\n    provides: Object.create(null),\n    components: Object.create(null),\n  };\n}\n\nexport function createAppAPI<HostElement>(\n  render: RootRenderFunction<HostElement>,\n): CreateAppFunction<HostElement> {\n  return function createApp(rootComponent) {\n    const context = createAppContext();\n    const installedPlugins = new Set();\n\n    const config: AppConfig = {\n      globalProperties: {},\n    };\n\n    const app: App = (context.app = {\n      _component: rootComponent,\n      _props: null,\n      _context: context,\n      config,\n\n      use(plugin: Plugin, ...options: any[]) {\n        // skip duplicate plugins\n        if (installedPlugins.has(plugin)) return app;\n\n        installedPlugins.add(plugin);\n        plugin.install(app, ...options);\n        return app;\n      },\n\n      component(name: string, component: Component) {\n        context.components[name] = component;\n        return app;\n      },\n\n      mount(rootContainer: HostElement) {\n        const vnode = createVNode(rootComponent as ComponentPublicInstance);\n        vnode.appContext = context;\n        render(vnode, rootContainer, null);\n      },\n\n      provide(key, value) {\n        context.provides[key as string | symbol] = value;\n        return app;\n      },\n    });\n\n    return app;\n  };\n}\n"
  },
  {
    "path": "impl/runtime-core/src/apiDefineComponent.ts",
    "content": "import { isFunction } from \"@chibivue/shared\";\nimport type { EmitsOptions } from \"./componentEmits\";\n\nimport type {\n  ComponentInjectOptions,\n  ComponentOptions,\n  ComputedOptions,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type {\n  ComponentPublicInstanceConstructor,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\n\ntype DefineComponent<\n  PropOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  Props = ResolveProps<PropOptions>,\n> = ComponentPublicInstanceConstructor<\n  CreateComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE>,\n  Props,\n  RawBindings,\n  D,\n  C,\n  M,\n  I,\n  S,\n  E,\n  EE\n>;\n\nexport function defineComponent<\n  PropsOptions = {},\n  RawBindings = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = {},\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n>(\n  options:\n    | ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>\n    | ComponentOptions<PropsOptions, RawBindings, D, C, M, I, S, E, EE>[\"setup\"],\n): DefineComponent<PropsOptions, RawBindings, D, C, M, I, S, E, EE> {\n  return (isFunction(options) ? { setup: options } : options) as any;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/apiInject.ts",
    "content": "import { isVapor } from \"@chibivue/runtime-vapor\";\nimport { currentInstance } from \"./component\";\n\nexport interface InjectionKey<T> extends Symbol {}\n\nexport function provide<T>(key: InjectionKey<T> | string | number, value: T): void {\n  if (!currentInstance) {\n    // do nothing\n  } else if (isVapor(currentInstance)) {\n    // do nothing\n  } else {\n    let provides = currentInstance.provides;\n    provides[key as string] = value;\n  }\n}\n\nexport function inject<T>(key: InjectionKey<T> | string): T | undefined;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;\nexport function inject<T>(key: InjectionKey<T> | string, defaultValue?: T | null): T | undefined {\n  const instance = currentInstance;\n  if (instance) {\n    const provides =\n      instance.parent == null ? instance.appContext?.provides : instance.parent.provides;\n    if (provides && (key as string | symbol) in provides) {\n      return provides[key as string];\n    }\n  }\n  return defaultValue as T | undefined;\n}\n\nexport function hasInjectionContext(): boolean {\n  return currentInstance !== null;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/apiLifecycle.ts",
    "content": "import type { VaporComponentInternalInstance } from \"@chibivue/runtime-vapor\";\nimport { type ComponentInternalInstance, currentInstance, setCurrentInstance } from \"./component\";\nimport { LifecycleHooks } from \"./enums\";\n\nexport function injectHook(\n  type: LifecycleHooks,\n  hook: Function,\n  target: ComponentInternalInstance | VaporComponentInternalInstance | null = currentInstance,\n): Function | undefined {\n  if (target) {\n    const hooks = (target as any)[type] || ((target as any)[type] = []);\n    const wrappedHook = (...args: unknown[]) => {\n      setCurrentInstance(target);\n      const res = hook(...args);\n      return res;\n    };\n    hooks.push(wrappedHook);\n    return wrappedHook;\n  }\n}\n\nexport const createHook =\n  <T extends Function = () => any>(lifecycle: LifecycleHooks) =>\n  (\n    hook: T,\n    target: ComponentInternalInstance | VaporComponentInternalInstance | null = currentInstance,\n  ): Function | undefined =>\n    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target);\n\ntype LifecycleHookFn<T extends Function = () => any> = (\n  hook: T,\n  target?: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => Function | undefined;\n\nexport const onBeforeMount: LifecycleHookFn = createHook(LifecycleHooks.BEFORE_MOUNT);\nexport const onMounted: LifecycleHookFn = createHook(LifecycleHooks.MOUNTED);\nexport const onBeforeUpdate: LifecycleHookFn = createHook(LifecycleHooks.BEFORE_UPDATE);\nexport const onUpdated: LifecycleHookFn = createHook(LifecycleHooks.UPDATED);\nexport const onBeforeUnmount: LifecycleHookFn = createHook(LifecycleHooks.BEFORE_UNMOUNT);\nexport const onUnmounted: LifecycleHookFn = createHook(LifecycleHooks.UNMOUNTED);\nexport const onActivated: LifecycleHookFn = createHook(LifecycleHooks.ACTIVATED);\nexport const onDeactivated: LifecycleHookFn = createHook(LifecycleHooks.DEACTIVATED);\n"
  },
  {
    "path": "impl/runtime-core/src/apiWatch.ts",
    "content": "import { type ComputedRef, ReactiveEffect, type Ref, isRef } from \"@chibivue/reactivity\";\nimport {\n  hasChanged,\n  isArray,\n  isFunction,\n  isMap,\n  isObject,\n  isPlainObject,\n  isSet,\n} from \"@chibivue/shared\";\n\nexport type WatchEffect = () => void;\n\nexport type WatchSource<T = any> = (() => T) | Ref<T> | ComputedRef<T>;\n\nexport type WatchCallback<V = any, OV = any> = (\n  value: V,\n  oldValue: OV,\n  onCleanup: OnCleanup,\n) => void;\n\nconst INITIAL_WATCHER_VALUE = {};\n\nexport interface WatchOptions<Immediate = boolean> {\n  immediate?: Immediate;\n  deep?: boolean;\n}\n\ntype OnCleanup = (cleanupFn: () => void) => void;\n\nexport function watch<T>(\n  source: WatchSource<T> | WatchSource[],\n  cb: WatchCallback,\n  option?: WatchOptions,\n): () => void {\n  return doWatch(source, cb, option);\n}\n\nexport function watchEffect(source: WatchEffect): void {\n  doWatch(source, null);\n}\n\nfunction doWatch(\n  source: WatchSource | WatchSource[] | WatchEffect,\n  cb: WatchCallback | null,\n  option: WatchOptions = {},\n) {\n  let getter: () => any;\n  let isMultiSource = false;\n  if (isFunction(source)) {\n    getter = () => source();\n  } else if (isRef(source)) {\n    getter = () => source.value;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    getter = () => source.map((s) => (isRef(s) ? s.value : s));\n  } else {\n    if (isObject(source)) option.deep = true;\n    getter = () => source;\n  }\n\n  if (cb && option.deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n\n  let cleanup: () => void;\n  let onCleanup: OnCleanup = (fn: () => void) => {\n    cleanup = effect.onStop = () => {\n      fn();\n    };\n  };\n\n  let oldValue: any = isMultiSource\n    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)\n    : INITIAL_WATCHER_VALUE;\n\n  const job = () => {\n    if (cb) {\n      const newValue = effect.run();\n      if (\n        option.deep ||\n        (isMultiSource\n          ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])?.[i]))\n          : hasChanged(newValue, oldValue))\n      ) {\n        // cleanup before running cb again\n        if (cleanup) {\n          cleanup();\n        }\n        cb(newValue, oldValue, onCleanup);\n        oldValue = newValue;\n      }\n    } else {\n      // watchEffect\n      effect.run();\n    }\n  };\n\n  const effect = new ReactiveEffect(getter, job);\n\n  // initial run\n  if (option.immediate) {\n    job();\n  } else {\n    oldValue = effect.run();\n  }\n\n  const unwatch = () => {\n    effect.stop();\n  };\n\n  return unwatch;\n}\n\nexport function traverse(value: unknown, seen?: Set<unknown>): unknown {\n  if (!isObject(value)) return value;\n\n  seen = seen || new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  if (isRef(value)) {\n    traverse(value.value, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v: any) => {\n      traverse(v, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], seen);\n    }\n  }\n  return value;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/component.ts",
    "content": "import { type VaporComponentInternalInstance, isVapor } from \"@chibivue/runtime-vapor\";\nimport { EffectScope, type ReactiveEffect, proxyRefs } from \"@chibivue/reactivity\";\nimport { isFunction, isObject } from \"@chibivue/shared\";\n\nimport { type AppContext, createAppContext } from \"./apiCreateApp\";\nimport { type EmitFn, type EmitsOptions, type ObjectEmitsOptions, emit } from \"./componentEmits\";\nimport { type ComponentOptions, applyOptions } from \"./componentOptions\";\nimport { type NormalizedProps, initProps } from \"./componentProps\";\nimport {\n  type ComponentPublicInstance,\n  PublicInstanceProxyHandlers,\n} from \"./componentPublicInstance\";\nimport {\n  type InternalSlots,\n  type SlotsType,\n  type UnwrapSlotsType,\n  initSlots,\n} from \"./componentSlots\";\nimport { LifecycleHooks } from \"./enums\";\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type Data = Record<string, unknown>;\n\nexport type Component = ConcreteComponent;\nexport type ConcreteComponent = ComponentOptions;\n\nexport type LifecycleHook<TFn = Function> = TFn[] | null;\n\nexport interface ComponentInternalInstance {\n  uid: number;\n  type: ConcreteComponent;\n  appContext: AppContext;\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null;\n\n  vnode: VNode;\n  next: VNode | null;\n\n  proxy: ComponentPublicInstance | null;\n  effect: ReactiveEffect;\n  scope: EffectScope;\n\n  components: Record<string, ConcreteComponent> | null;\n\n  propsOptions: NormalizedProps;\n  emitsOptions: ObjectEmitsOptions | null;\n\n  subTree: VNode;\n  render: InternalRenderFunction | null;\n  update: () => void;\n\n  provides: Data;\n\n  exposed: Record<string, any> | null;\n  exposeProxy: Record<string, any> | null;\n  ctx: Data;\n  data: Data;\n  props: Data;\n  slots: InternalSlots;\n  emit: EmitFn;\n\n  setupState: Data;\n  setupContext: SetupContext | null;\n\n  // lifecycle\n  isMounted: boolean;\n  isDeactivated: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n  [LifecycleHooks.ACTIVATED]: LifecycleHook;\n  [LifecycleHooks.DEACTIVATED]: LifecycleHook;\n}\n\nexport type SetupContext<E = EmitsOptions, S extends SlotsType = {}> = {\n  slots: UnwrapSlotsType<S>;\n  emit: EmitFn<E>;\n  expose: (exposed?: Record<string, any>) => void;\n};\n\nexport type InternalRenderFunction = {\n  (\n    ctx: ComponentPublicInstance,\n    $data: ComponentInternalInstance[\"data\"],\n    $options: ComponentInternalInstance[\"ctx\"],\n  ): VNodeChild;\n};\n\nlet uid = 0;\nexport function createComponentInstance(\n  vnode: VNode,\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n): ComponentInternalInstance {\n  const type = vnode.type as ConcreteComponent;\n  const appContext = (parent ? parent.appContext : vnode.appContext) || createAppContext();\n\n  const instance: ComponentInternalInstance = {\n    uid: uid++,\n    type,\n    parent,\n    vnode,\n    appContext,\n    next: null,\n    proxy: null,\n    effect: null!,\n    scope: new EffectScope(),\n    subTree: null!,\n    update: null!,\n    render: null!,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    components: null,\n    propsOptions: type.props || {},\n    emitsOptions: type.emits || {},\n    emit: null!, // to be set immediately\n\n    exposed: null,\n    exposeProxy: null,\n    ctx: {},\n    data: {},\n    props: {},\n    slots: {},\n\n    setupState: {},\n    setupContext: null,\n\n    isMounted: false,\n    isDeactivated: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n    [LifecycleHooks.ACTIVATED]: null,\n    [LifecycleHooks.DEACTIVATED]: null,\n  };\n\n  instance.ctx = { _: instance };\n  instance.emit = emit.bind(null, instance);\n\n  return instance;\n}\n\nexport let currentInstance: ComponentInternalInstance | VaporComponentInternalInstance | null =\n  null;\nexport const getCurrentInstance: () =>\n  | ComponentInternalInstance\n  | VaporComponentInternalInstance\n  | null = () => currentInstance;\n\nexport const setCurrentInstance = (\n  instance: ComponentInternalInstance | VaporComponentInternalInstance,\n): void => {\n  currentInstance = instance;\n  if (isVapor(instance)) {\n    // do nothing\n  } else {\n    instance.scope?.on();\n  }\n};\n\nexport const unsetCurrentInstance = (): void => {\n  if (currentInstance && isVapor(currentInstance)) {\n    // do nothing\n  } else {\n    currentInstance && currentInstance.scope?.off();\n  }\n  currentInstance = null;\n};\n\nexport const setupComponent = (instance: ComponentInternalInstance): void => {\n  const { props, children } = instance.vnode;\n  initProps(instance, props);\n  initSlots(instance, children);\n\n  const Component = instance.type as ComponentOptions;\n\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n\n  // Composition API\n  const { setup } = Component;\n  if (setup) {\n    const setupContext = (instance.setupContext = createSetupContext(instance));\n    setCurrentInstance(instance);\n    const setupResult = setup(instance.props, setupContext);\n    if (isFunction(setupResult)) {\n      instance.render = setupResult as InternalRenderFunction;\n    } else if (isObject(setupResult)) {\n      instance.setupState = proxyRefs(setupResult);\n    }\n    unsetCurrentInstance();\n  }\n\n  if (compile && !Component.render) {\n    const template = Component.template ?? \"\";\n    if (template) {\n      instance.render = compile(template);\n    }\n  }\n\n  if (Component.render) {\n    instance.render = Component.render as any;\n  }\n\n  // Options API\n  setCurrentInstance(instance);\n  applyOptions(instance);\n  unsetCurrentInstance();\n};\n\nexport function getExposeProxy(\n  instance: ComponentInternalInstance,\n): Record<string, any> | undefined {\n  if (instance.exposed) {\n    return (\n      instance.exposeProxy ||\n      (instance.exposeProxy = new Proxy(proxyRefs(instance.exposed), {\n        get(target, key: string) {\n          if (key in target) {\n            return target[key];\n          }\n        },\n        has(target, key: string) {\n          return key in target;\n        },\n      }))\n    );\n  }\n}\n\ntype CompileFunction = (template: string | object) => InternalRenderFunction;\nlet compile: CompileFunction | undefined;\n\nexport function registerRuntimeCompiler(_compile: any): void {\n  compile = _compile;\n}\n\nexport function createSetupContext(instance: ComponentInternalInstance): SetupContext {\n  const expose: SetupContext[\"expose\"] = (exposed) => {\n    instance.exposed = exposed || {};\n  };\n  return { slots: instance.slots, emit: instance.emit, expose };\n}\n"
  },
  {
    "path": "impl/runtime-core/src/componentEmits.ts",
    "content": "import { type UnionToIntersection, camelize, toHandlerKey } from \"@chibivue/shared\";\nimport type { ComponentInternalInstance } from \"./component\";\n\nexport type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>;\n\nexport type EmitsOptions = ObjectEmitsOptions | string[];\n\nexport type EmitFn<Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options> =\n  Options extends Array<infer V>\n    ? (event: V, ...args: any[]) => void\n    : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function\n      ? (event: string, ...args: any[]) => void\n      : UnionToIntersection<\n          {\n            [key in Event]: Options[key] extends (...args: infer Args) => any\n              ? (event: key, ...args: Args) => void\n              : (event: key, ...args: any[]) => void;\n          }[Event]\n        >;\n\nexport function emit(instance: ComponentInternalInstance, event: string, ...rawArgs: any[]): void {\n  const props = instance.vnode.props || {};\n  let args = rawArgs;\n\n  let handler = props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))];\n\n  if (handler) handler(...args);\n}\n"
  },
  {
    "path": "impl/runtime-core/src/componentOptions.ts",
    "content": "import { isArray, isFunction, isObject, isString } from \"@chibivue/shared\";\n\nimport {\n  type ComputedGetter,\n  type Ref,\n  type WritableComputedOptions,\n  computed,\n  isRef,\n  reactive,\n} from \"@chibivue/reactivity\";\n\nimport { inject, provide } from \"./apiInject\";\nimport {\n  onBeforeMount,\n  onBeforeUnmount,\n  onBeforeUpdate,\n  onMounted,\n  onUnmounted,\n  onUpdated,\n} from \"./apiLifecycle\";\nimport { type WatchCallback, type WatchOptions, watch } from \"./apiWatch\";\n\nimport type { ComponentInternalInstance, ConcreteComponent, Data, SetupContext } from \"./component\";\nimport type {\n  ComponentPublicInstance,\n  CreateComponentPublicInstance,\n} from \"./componentPublicInstance\";\nimport type { SlotsType } from \"./componentSlots\";\nimport type { PropType } from \"./componentProps\";\nimport type { EmitsOptions, ObjectEmitsOptions } from \"./componentEmits\";\n\nimport type { VNode, VNodeChild } from \"./vnode\";\n\nexport type RenderFunction = () => VNodeChild;\n\nexport type ComponentOptions<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = ComponentInjectOptions,\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n  II extends string = string,\n> = {\n  name?: string;\n  props?: P;\n  data?: (this: ComponentPublicInstance<ResolveProps<P>, B>) => D;\n  computed?: C;\n  methods?: M;\n  watch?: ComponentWatchOptions;\n  provide?: ComponentProvideOptions;\n  inject?: I | II[];\n  emits?: ObjectEmitsOptions;\n  slots?: S;\n  setup?: (props: ResolveProps<P>, ctx: SetupContext<E, S>) => (() => VNode) | B;\n  render?: (ctx: CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>) => VNode;\n  template?: string;\n  components?: Record<string, ConcreteComponent>;\n\n  beforeCreate?(): void;\n  created?(): void;\n  beforeMount?(): void;\n  mounted?(): void;\n  beforeUpdate?(): void;\n  updated?(): void;\n  beforeUnmount?(): void;\n  unmounted?(): void;\n} & ThisType<CreateComponentPublicInstance<ResolveProps<P>, B, D, C, M, I, S, E, EE>>;\n\nexport type ResolveProps<T> = { [K in keyof T]: InferPropType<T[K]> };\ntype InferPropType<T> = T extends { type: PropType<infer U> } ? U : never;\n\nexport type ComputedOptions = Record<string, ComputedGetter<any> | WritableComputedOptions<any>>;\n\nexport type ExtractComputedReturns<T extends any> = {\n  [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }\n    ? TReturn\n    : T[key] extends (...args: any[]) => infer TReturn\n      ? TReturn\n      : never;\n};\n\nexport interface MethodOptions {\n  [key: string]: Function;\n}\n\nexport type ObjectWatchOptionItem = {\n  handler: WatchCallback | string;\n} & WatchOptions;\n\ntype WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem;\n\ntype ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[];\n\ntype ComponentWatchOptions = Record<string, ComponentWatchOptionItem>;\n\nexport type ComponentProvideOptions = ObjectProvideOptions | Function;\n\ntype ObjectProvideOptions = Record<string | symbol, unknown>;\n\ntype ObjectInjectOptions = Record<\n  string | symbol,\n  string | symbol | { from?: string | symbol; default?: unknown }\n>;\n\nexport type ComponentInjectOptions = string[] | ObjectInjectOptions;\n\nexport type InjectToObject<T extends ComponentInjectOptions> = T extends string[]\n  ? {\n      [K in T[number]]?: unknown;\n    }\n  : T extends ObjectInjectOptions\n    ? {\n        [K in keyof T]?: unknown;\n      }\n    : never;\n\nexport function applyOptions(instance: ComponentInternalInstance): void {\n  const { type: options } = instance;\n  const publicThis = instance.proxy! as any;\n  const ctx = instance.ctx;\n\n  const {\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    beforeUnmount,\n    unmounted,\n  } = options;\n\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx);\n  }\n\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        ctx[key] = methodHandler.bind(publicThis);\n      }\n    }\n  }\n\n  if (dataOptions) {\n    const data = dataOptions.call(publicThis);\n    instance.data = reactive(data);\n  }\n\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = (computedOptions as ComputedOptions)[key];\n\n      const get = isFunction(opt)\n        ? opt.bind(publicThis, publicThis)\n        : isFunction(opt.get)\n          ? opt.get.bind(publicThis, publicThis)\n          : () => {};\n\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {};\n\n      const c = computed({ get, set });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => (c.value = v),\n      });\n    }\n  }\n\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n\n  created?.();\n\n  function registerLifecycleHook(register: Function, hook?: Function | Function[]) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n}\n\nexport function resolveInjections(injectOptions: ComponentInjectOptions, ctx: any): void {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions)!;\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected: unknown;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(opt.from || key);\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      // unwrap injected refs\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => (injected as Ref).value,\n        set: (v) => ((injected as Ref).value = v),\n      });\n    } else {\n      ctx[key] = injected;\n    }\n  }\n}\n\nfunction normalizeInject(raw: ComponentInjectOptions | undefined): ObjectInjectOptions | undefined {\n  if (isArray(raw)) {\n    const res: ObjectInjectOptions = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\n\nexport function createWatcher(\n  raw: ComponentWatchOptionItem,\n  ctx: Data,\n  publicThis: ComponentPublicInstance,\n  key: string,\n): void {\n  const getter = () => (publicThis as any)[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler as WatchCallback);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler)\n        ? raw.handler.bind(publicThis)\n        : (ctx[raw.handler] as WatchCallback);\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/componentProps.ts",
    "content": "import { reactive } from \"@chibivue/reactivity\";\nimport { camelize, hasOwn, isReservedProp } from \"@chibivue/shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\n\nexport type NormalizedProps = Record<string, PropOptions | null>;\nexport interface PropOptions<T = any> {\n  type?: PropType<T> | true | null;\n  required?: boolean;\n  default?: null | undefined | object;\n}\nexport type PropType<T> = { new (...args: any[]): T & {} } | { (): T };\n\nexport function initProps(instance: ComponentInternalInstance, rawProps: Data | null): void {\n  const props: Data = {};\n  setFullProps(instance, rawProps, props);\n  instance.props = reactive(props);\n}\n\nexport function updateProps(instance: ComponentInternalInstance, rawProps: Data | null): void {\n  const { props } = instance;\n  Object.entries(rawProps ?? {}).forEach(([key, value]) => {\n    props[camelize(key)] = value;\n  });\n}\n\nfunction setFullProps(instance: ComponentInternalInstance, rawProps: Data | null, props: Data) {\n  const options = instance.propsOptions;\n\n  if (rawProps) {\n    for (let key in rawProps) {\n      // skip reserved properties (eg. ref, key)\n      if (isReservedProp(key)) continue;\n\n      const value = rawProps[key];\n      // kebab -> camel\n      let camelKey;\n      if (options && hasOwn(options, (camelKey = camelize(key)))) {\n        props[camelKey] = value;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/componentPublicInstance.ts",
    "content": "import { hasOwn } from \"@chibivue/shared\";\nimport type { ComponentInternalInstance, Data } from \"./component\";\nimport type { EmitFn, EmitsOptions } from \"./componentEmits\";\nimport type {\n  ComponentInjectOptions,\n  ComputedOptions,\n  ExtractComputedReturns,\n  InjectToObject,\n  MethodOptions,\n  ResolveProps,\n} from \"./componentOptions\";\nimport type { SlotsType, UnwrapSlotsType } from \"./componentSlots\";\nimport type { nextTick } from \"./scheduler\";\n\nexport type ComponentPublicInstanceConstructor<\n  T extends ComponentPublicInstance<Props, RawBindings, D, C, M, I, S, E, EE> =\n    ComponentPublicInstance<any>,\n  Props = any,\n  RawBindings = any,\n  D = any,\n  C extends ComputedOptions = any,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = {\n  new (...args: any[]): T;\n};\n\nexport type CreateComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = ComputedOptions,\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  EE extends string = string,\n> = ComponentPublicInstance<P, B, D, C, M, I, S, E, EE>;\n\nexport type ComponentPublicInstance<\n  P = {},\n  B = {},\n  D = {},\n  C extends ComputedOptions = {},\n  M extends MethodOptions = MethodOptions,\n  I extends ComponentInjectOptions = {},\n  S extends SlotsType = {},\n  E extends EmitsOptions = {},\n  _EE extends string = string,\n> = {\n  $: ComponentInternalInstance;\n  $data: D;\n  $props: ResolveProps<P>;\n  $slots: UnwrapSlotsType<S>;\n  $parent: ComponentPublicInstance | null;\n  $emit: EmitFn<E>;\n  $el: any;\n  $forceUpdate: () => void;\n  $nextTick: typeof nextTick;\n} & P &\n  B &\n  D &\n  M &\n  ExtractComputedReturns<C> &\n  InjectToObject<I>;\n\nexport interface ComponentRenderContext {\n  [key: string]: any;\n  _: ComponentInternalInstance;\n}\n\nconst hasSetupBinding = (state: Data, key: string) => hasOwn(state, key);\n\nexport const PublicInstanceProxyHandlers: ProxyHandler<any> = {\n  get({ _: instance }: ComponentRenderContext, key: string) {\n    const { ctx, setupState, data, props } = instance;\n\n    let normalizedProps;\n    if (hasSetupBinding(setupState, key)) {\n      return setupState[key];\n    } else if (hasOwn(data, key)) {\n      return data[key];\n    } else if ((normalizedProps = instance.propsOptions) && hasOwn(normalizedProps, key)) {\n      return props![key];\n    } else if (hasOwn(ctx, key)) {\n      return ctx[key];\n    }\n  },\n  set({ _: instance }: ComponentRenderContext, key: string, value: any): boolean {\n    const { ctx, data, setupState } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (hasOwn(data, key)) {\n      data[key] = value;\n    } else if (hasOwn(ctx, key)) {\n      ctx[key] = value;\n    }\n    return true;\n  },\n\n  has({ _: { setupState, ctx, propsOptions } }: ComponentRenderContext, key: string) {\n    let normalizedProps;\n    return (\n      hasOwn(setupState, key) ||\n      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||\n      hasOwn(ctx, key)\n    );\n  },\n};\n"
  },
  {
    "path": "impl/runtime-core/src/componentRenderContext.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\n\nexport let currentRenderingInstance: ComponentInternalInstance | null = null;\n\nexport function setCurrentRenderingInstance(\n  instance: ComponentInternalInstance | null,\n): ComponentInternalInstance | null {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  return prev;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/componentRenderUtils.ts",
    "content": "import type { ComponentInternalInstance } from \"./component\";\nimport { setCurrentRenderingInstance } from \"./componentRenderContext\";\nimport { type VNode, normalizeVNode } from \"./vnode\";\n\nexport function renderComponentRoot(instance: ComponentInternalInstance): VNode {\n  setCurrentRenderingInstance(instance);\n  const { proxy, render, data, ctx } = instance;\n  const result = normalizeVNode(render!.call(proxy, proxy!, data, ctx));\n  return result;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/componentSlots.ts",
    "content": "import { toRaw } from \"@chibivue/reactivity\";\nimport type { IfAny, Prettify } from \"@chibivue/shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { VNode, VNodeNormalizedChildren } from \"./vnode\";\n\nexport type Slot<T extends any = any> = (\n  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>\n) => VNode[];\n\nexport type InternalSlots = {\n  [name: string]: Slot | undefined;\n};\n\nexport type Slots = Readonly<InternalSlots>;\n\ndeclare const SlotSymbol: unique symbol;\nexport type SlotsType<T extends Record<string, any> = Record<string, any>> = {\n  [SlotSymbol]?: T;\n};\n\nexport type RawSlots = {\n  [name: string]: unknown;\n};\n\nexport type UnwrapSlotsType<S extends SlotsType, T = NonNullable<S[typeof SlotSymbol]>> = [\n  keyof S,\n] extends [never]\n  ? Slots\n  : Readonly<\n      Prettify<{\n        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? T[K] : Slot<T[K]>;\n      }>\n    >;\n\nexport const initSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n): void => {\n  if (children === null || children === undefined) {\n    instance.slots = {};\n  } else if (Array.isArray(children)) {\n    // Array children should be converted to a default slot function\n    instance.slots = {\n      default: () => children as VNode[],\n    };\n  } else if (typeof children === \"object\") {\n    // Already an object with slot functions\n    instance.slots = toRaw(children as InternalSlots);\n  } else {\n    instance.slots = {};\n  }\n};\n\nexport const updateSlots = (\n  instance: ComponentInternalInstance,\n  children: VNodeNormalizedChildren,\n): void => {\n  if (children === null || children === undefined) {\n    instance.slots = {};\n  } else if (Array.isArray(children)) {\n    // Array children should be converted to a default slot function\n    instance.slots = {\n      default: () => children as VNode[],\n    };\n  } else if (typeof children === \"object\") {\n    // Already an object with slot functions\n    instance.slots = toRaw(children as InternalSlots);\n  } else {\n    instance.slots = {};\n  }\n};\n"
  },
  {
    "path": "impl/runtime-core/src/components/KeepAlive.ts",
    "content": "import { ShapeFlags, isArray, isString } from \"@chibivue/shared\";\nimport type { ComponentInternalInstance, Data } from \"../component\";\nimport { getCurrentInstance } from \"../component\";\nimport type { VNode } from \"../vnode\";\nimport { cloneVNode, isSameVNodeType } from \"../vnode\";\nimport { queuePostFlushCb } from \"../scheduler\";\nimport { setCurrentInstance, unsetCurrentInstance } from \"../component\";\n\nexport interface KeepAliveProps {\n  include?: MatchPattern;\n  exclude?: MatchPattern;\n  max?: number | string;\n}\n\ntype MatchPattern = string | RegExp | (string | RegExp)[];\n\nexport interface KeepAliveContext extends ComponentInternalInstance {\n  renderer: KeepAliveRenderer;\n  activate: (\n    vnode: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  deactivate: (vnode: VNode) => void;\n}\n\ninterface KeepAliveRenderer {\n  p: (\n    n1: VNode | null,\n    n2: VNode,\n    container: any,\n    anchor: any | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  m: (vnode: VNode, container: any, anchor: any | null) => void;\n  um: (vnode: VNode) => void;\n  o: {\n    createElement: (type: string) => any;\n  };\n}\n\nconst KeepAliveImpl = {\n  name: `KeepAlive`,\n  __isKeepAlive: true,\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number],\n  },\n  setup(props: KeepAliveProps, _setupContext: { slots: any }) {\n    const instance = getCurrentInstance()! as KeepAliveContext;\n    // Note: We access instance.slots directly in the render function\n    // instead of using the destructured slots from setupContext,\n    // because slots can be updated during component updates.\n    const cache: Map<any, VNode> = new Map();\n    const keys: Set<any> = new Set();\n    let current: VNode | null = null;\n\n    const parentComponent = instance.parent as ComponentInternalInstance;\n\n    // Create a hidden container for holding deactivated components\n    const storageContainer = instance.renderer.o.createElement(\"div\");\n\n    instance.activate = (vnode, container, anchor, _parentComponent) => {\n      const instance = vnode.component!;\n      move(vnode, container, anchor);\n      // in case props have changed\n      patch(instance.vnode, vnode, container, anchor, parentComponent);\n      queuePostFlushCb(() => {\n        instance.isDeactivated = false;\n        if (instance.a) {\n          instance.a.forEach((hook) => (hook as () => void)());\n        }\n      });\n    };\n\n    instance.deactivate = (vnode: VNode) => {\n      move(vnode, storageContainer, null);\n      queuePostFlushCb(() => {\n        const instance = vnode.component!;\n        if (instance.da) {\n          instance.da.forEach((hook) => (hook as () => void)());\n        }\n        instance.isDeactivated = true;\n      });\n    };\n\n    function move(vnode: VNode, container: any, anchor: any | null): void {\n      instance.renderer.m(vnode, container, anchor);\n    }\n\n    function patch(\n      n1: VNode | null,\n      n2: VNode,\n      container: any,\n      anchor: any | null,\n      parentComponent: ComponentInternalInstance | null,\n    ): void {\n      instance.renderer.p(n1, n2, container, anchor, parentComponent);\n    }\n\n    function unmount(vnode: VNode): void {\n      resetShapeFlag(vnode);\n      instance.renderer.um(vnode);\n    }\n\n    function pruneCache(filter?: (name: string) => boolean): void {\n      cache.forEach((vnode, key) => {\n        const name = getComponentName(vnode.type as any);\n        if (name && (!filter || !filter(name))) {\n          pruneCacheEntry(key);\n        }\n      });\n    }\n\n    function pruneCacheEntry(key: any): void {\n      const cached = cache.get(key) as VNode;\n      if (!current || !isSameVNodeType(cached, current)) {\n        unmount(cached);\n      } else if (current) {\n        resetShapeFlag(current);\n      }\n      cache.delete(key);\n      keys.delete(key);\n    }\n\n    return (): VNode | undefined => {\n      // Access instance.slots directly to get the latest slots after updates\n      const slots = instance.slots;\n      if (!slots.default) {\n        return undefined;\n      }\n\n      const children = slots.default();\n\n      // Filter out text nodes and find the first component vnode\n      const vnodes = children.filter(\n        (c) => c && typeof c === \"object\" && c.shapeFlag !== undefined,\n      );\n      const rawVNode = vnodes[0];\n\n      if (!rawVNode) {\n        current = null;\n        return undefined;\n      }\n\n      if (vnodes.length > 1) {\n        current = null;\n        return children as unknown as VNode;\n      } else if (\n        !(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&\n        !(rawVNode.shapeFlag & ShapeFlags.COMPONENT)\n      ) {\n        current = null;\n        return rawVNode;\n      }\n\n      let vnode = rawVNode;\n      const comp = vnode.type as any;\n      const name = getComponentName(comp);\n      const { include, exclude, max } = props;\n\n      if (\n        (include && (!name || !matches(include, name))) ||\n        (exclude && name && matches(exclude, name))\n      ) {\n        current = vnode;\n        return rawVNode;\n      }\n\n      const key = vnode.key == null ? comp : vnode.key;\n      const cachedVNode = cache.get(key);\n\n      if (cachedVNode) {\n        vnode.el = cachedVNode.el;\n        vnode.component = cachedVNode.component;\n        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;\n        keys.delete(key);\n        keys.add(key);\n      } else {\n        // Cache the vnode for future reactivation\n        cache.set(key, vnode);\n        keys.add(key);\n        if (max && keys.size > parseInt(max as string, 10)) {\n          pruneCacheEntry(keys.values().next().value);\n        }\n      }\n\n      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n      current = vnode;\n      return vnode;\n    };\n  },\n};\n\nexport const KeepAlive = KeepAliveImpl as any as {\n  __isKeepAlive: true;\n  new (): {\n    $props: KeepAliveProps;\n  };\n};\n\nexport function isKeepAlive(vnode: VNode): boolean {\n  return (vnode.type as any).__isKeepAlive === true;\n}\n\nfunction matches(pattern: MatchPattern, name: string): boolean {\n  if (isArray(pattern)) {\n    return pattern.some((p: string | RegExp) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (pattern instanceof RegExp) {\n    return pattern.test(name);\n  }\n  return false;\n}\n\nfunction resetShapeFlag(vnode: VNode): void {\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n  vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;\n}\n\nfunction getComponentName(comp: { name?: string; __name?: string }): string | undefined {\n  return comp.name || comp.__name;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/components/Teleport.ts",
    "content": "import { ShapeFlags } from \"@chibivue/shared\";\nimport type { RendererElement, RendererNode, RendererOptions } from \"../renderer\";\nimport type { VNode, VNodeArrayChildren } from \"../vnode\";\nimport type { ComponentInternalInstance } from \"../component\";\n\nexport const TeleportSymbol: unique symbol = Symbol();\n\nexport interface TeleportProps {\n  to: string | RendererElement | null | undefined;\n  disabled?: boolean;\n}\n\nexport const isTeleport = (type: any): boolean => type.__isTeleport;\n\nexport interface TeleportImpl {\n  __isTeleport: true;\n  process: (\n    n1: TeleportVNode | null,\n    n2: TeleportVNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n    internals: TeleportInternals,\n  ) => void;\n  remove: (vnode: TeleportVNode, internals: TeleportInternals) => void;\n  move: typeof moveTeleport;\n}\n\nexport const Teleport: TeleportImpl = {\n  __isTeleport: true,\n  process(\n    n1: TeleportVNode | null,\n    n2: TeleportVNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n    internals: TeleportInternals,\n  ) {\n    const {\n      mc: mountChildren,\n      pc: patchChildren,\n      pbc: patchBlockChildren,\n      o: { insert, querySelector, createText, createComment },\n    } = internals;\n\n    const disabled = isTeleportDisabled(n2.props);\n    const { shapeFlag, children } = n2;\n\n    if (n1 == null) {\n      // mount\n      const placeholder = (n2.el = createComment(\"teleport start\"));\n      const mainAnchor = (n2.anchor = createComment(\"teleport end\"));\n      insert(placeholder, container, anchor);\n      insert(mainAnchor, container, anchor);\n\n      const target = (n2.target = resolveTarget(n2.props, querySelector));\n      const targetAnchor = (n2.targetAnchor = createText(\"\"));\n\n      if (target) {\n        insert(targetAnchor, target);\n      }\n\n      const mount = (container: RendererElement, anchor: RendererNode | null) => {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(children as VNodeArrayChildren, container, anchor, parentComponent);\n        }\n      };\n\n      if (disabled) {\n        mount(container, mainAnchor);\n      } else if (target) {\n        mount(target, targetAnchor);\n      }\n    } else {\n      // update\n      n2.el = n1.el;\n      const mainAnchor = (n2.anchor = n1.anchor!);\n      const target = (n2.target = n1.target!);\n      const targetAnchor = (n2.targetAnchor = n1.targetAnchor!);\n      const wasDisabled = isTeleportDisabled(n1.props);\n      const currentContainer = wasDisabled ? container : target;\n      const currentAnchor = wasDisabled ? mainAnchor : targetAnchor;\n\n      patchChildren(n1, n2, currentContainer, currentAnchor, parentComponent);\n\n      if (disabled) {\n        if (!wasDisabled) {\n          // disabled -> enabled\n          moveTeleport(n2, container, mainAnchor, internals, TeleportMoveTypes.TOGGLE);\n        }\n      } else {\n        if (wasDisabled) {\n          // enabled -> disabled\n          moveTeleport(n2, target, targetAnchor, internals, TeleportMoveTypes.TOGGLE);\n        } else if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {\n          // target changed\n          const nextTarget = (n2.target = resolveTarget(n2.props, querySelector));\n          if (nextTarget) {\n            moveTeleport(n2, nextTarget, null, internals, TeleportMoveTypes.TARGET_CHANGE);\n          }\n        }\n      }\n    }\n  },\n\n  remove(vnode: TeleportVNode, { um: unmount, o: { remove: hostRemove } }: TeleportInternals) {\n    const { shapeFlag, children, anchor, targetAnchor, target } = vnode;\n    if (target) {\n      hostRemove(targetAnchor!);\n    }\n    hostRemove(anchor!);\n    if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        unmount((children as VNode[])[i]);\n      }\n    }\n  },\n\n  move: moveTeleport,\n};\n\nexport interface TeleportInternals {\n  mc: (\n    children: VNodeArrayChildren,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  pc: (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  pbc: (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | null,\n  ) => void;\n  um: (vnode: VNode) => void;\n  m: (vnode: VNode, container: RendererElement, anchor: RendererNode | null) => void;\n  o: RendererOptions;\n}\n\nexport type TeleportVNode = VNode<RendererNode> & { props: TeleportProps | null };\n\nfunction isTeleportDisabled(props: TeleportProps | null): boolean {\n  return props !== null && !!props.disabled;\n}\n\nfunction resolveTarget<T = RendererElement>(\n  props: TeleportProps | null,\n  select: RendererOptions[\"querySelector\"],\n): T | null {\n  const targetSelector = props && props.to;\n  if (typeof targetSelector === \"string\") {\n    if (!select) {\n      return null;\n    } else {\n      const target = select(targetSelector);\n      return target as T;\n    }\n  } else {\n    return targetSelector as T | null;\n  }\n}\n\nexport const enum TeleportMoveTypes {\n  TARGET_CHANGE,\n  TOGGLE,\n  REORDER,\n}\n\nfunction moveTeleport(\n  vnode: TeleportVNode,\n  container: RendererElement,\n  parentAnchor: RendererNode | null,\n  { o: { insert }, m: move }: TeleportInternals,\n  moveType: TeleportMoveTypes = TeleportMoveTypes.REORDER,\n): void {\n  if (moveType === TeleportMoveTypes.TARGET_CHANGE) {\n    insert(vnode.targetAnchor!, container, parentAnchor);\n  }\n\n  const { el, anchor, shapeFlag, children } = vnode;\n  const isReorder = moveType === TeleportMoveTypes.REORDER;\n  if (isReorder) {\n    insert(el!, container, parentAnchor);\n  }\n  if (!isReorder || isTeleportDisabled(vnode.props)) {\n    if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, parentAnchor);\n      }\n    }\n  }\n  if (isReorder) {\n    insert(anchor!, container, parentAnchor);\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/directives.ts",
    "content": "import { isFunction } from \"@chibivue/shared\";\nimport type { ComponentInternalInstance } from \"./component\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport { currentRenderingInstance } from \"./componentRenderContext\";\nimport type { VNode } from \"./vnode\";\n\nexport interface DirectiveBinding<V = any> {\n  instance: ComponentPublicInstance | null;\n  value: V;\n  oldValue: V | null;\n  arg?: string;\n  dir: ObjectDirective<any>;\n}\n\nexport type DirectiveHook<T = any> = (\n  el: T,\n  binding: DirectiveBinding,\n  vnode: VNode,\n  prevVNode: VNode | null,\n) => void;\n\nexport interface ObjectDirective<T = any> {\n  created?: DirectiveHook<T>;\n  beforeMount?: DirectiveHook<T>;\n  mounted?: DirectiveHook<T>;\n  beforeUpdate?: DirectiveHook<T>;\n  updated?: DirectiveHook<T>;\n  beforeUnmount?: DirectiveHook<T>;\n  unmounted?: DirectiveHook<T>;\n  deep?: boolean;\n}\n\nexport type DirectiveArguments = Array<\n  | [ObjectDirective | undefined]\n  | [ObjectDirective | undefined, any]\n  | [ObjectDirective | undefined, any, string]\n>;\n\nexport function withDirectives<T extends VNode>(vnode: T, directives: DirectiveArguments): T {\n  const internalInstance = currentRenderingInstance;\n  if (internalInstance === null) return vnode;\n\n  const instance = internalInstance.proxy;\n\n  const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = []);\n  for (let i = 0; i < directives.length; i++) {\n    let [dir, value, arg] = directives[i];\n    if (dir) {\n      if (isFunction(dir)) {\n        dir = {\n          mounted: dir,\n          updated: dir,\n        } as ObjectDirective;\n      }\n      bindings.push({\n        dir,\n        instance,\n        value,\n        oldValue: void 0,\n        arg,\n      });\n    }\n  }\n  return vnode;\n}\n\nexport function invokeDirectiveHook(\n  vnode: VNode,\n  prevVNode: VNode | null,\n  name: keyof ObjectDirective,\n): void {\n  const bindings = vnode.dirs!;\n  const oldBindings = prevVNode && prevVNode.dirs!;\n  for (let i = 0; i < bindings.length; i++) {\n    const binding = bindings[i];\n    if (oldBindings) {\n      binding.oldValue = oldBindings[i].value;\n    }\n\n    const hook = binding.dir[name] as DirectiveHook | undefined;\n    if (hook) {\n      hook(vnode.el, binding, vnode, prevVNode);\n    }\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/enums.ts",
    "content": "export const enum LifecycleHooks {\n  BEFORE_MOUNT = \"bm\",\n  MOUNTED = \"m\",\n  BEFORE_UPDATE = \"bu\",\n  UPDATED = \"u\",\n  BEFORE_UNMOUNT = \"bum\",\n  UNMOUNTED = \"um\",\n  ACTIVATED = \"a\",\n  DEACTIVATED = \"da\",\n}\n"
  },
  {
    "path": "impl/runtime-core/src/h.ts",
    "content": "import { isArray, isObject } from \"@chibivue/shared\";\nimport { type VNode, createVNode, isVNode } from \"./vnode\";\n\nexport function h(type: any, propsOrChildren?: any, children?: any): VNode {\n  const l = arguments.length;\n  if (l === 2) {\n    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {\n      // single vnode without props\n      if (isVNode(propsOrChildren)) {\n        return createVNode(type, null, [propsOrChildren]);\n      }\n      // props without children\n      return createVNode(type, propsOrChildren);\n    } else {\n      // omit props\n      return createVNode(type, null, propsOrChildren);\n    }\n  } else {\n    if (l > 3) {\n      children = Array.prototype.slice.call(arguments, 2);\n    } else if (l === 3 && isVNode(children)) {\n      children = [children];\n    }\n    return createVNode(type, propsOrChildren, children);\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/helpers/renderList.ts",
    "content": "import type { VNodeChild } from \"../vnode\";\n\nexport function renderList<T>(\n  source: T[],\n  renderItem: (value: T, index: number) => VNodeChild,\n): VNodeChild[] {\n  return source.map(renderItem);\n}\n"
  },
  {
    "path": "impl/runtime-core/src/helpers/resolveAssets.ts",
    "content": "import { isVapor } from \"@chibivue/runtime-vapor\";\nimport { camelize, capitalize } from \"@chibivue/shared\";\n\nimport { type ConcreteComponent, currentInstance } from \"../component\";\nimport type { ComponentOptions } from \"../componentOptions\";\nimport { currentRenderingInstance } from \"../componentRenderContext\";\nimport { KeepAlive } from \"../components/KeepAlive\";\n\nexport const COMPONENTS = \"components\";\n\nexport type AssetTypes = typeof COMPONENTS;\n\n// Built-in components that can be resolved by name\nconst builtInComponents: Record<string, ConcreteComponent> = {\n  KeepAlive,\n};\n\nexport function resolveComponent(name: string): ConcreteComponent | string {\n  // Check built-in components first\n  if (builtInComponents[name]) {\n    return builtInComponents[name];\n  }\n  return resolveAsset(COMPONENTS, name) || name;\n}\n\nfunction resolveAsset(type: AssetTypes, name: string) {\n  const instance = currentRenderingInstance || currentInstance;\n\n  if (instance && !isVapor(instance)) {\n    const Component = instance.type;\n    const res =\n      // local registration\n      resolve(instance[type] || (Component as ComponentOptions)[type], name) ||\n      // global registration\n      resolve(instance.appContext[type], name);\n\n    return res;\n  }\n}\n\nfunction resolve(registry: Record<string, any> | undefined, name: string) {\n  return (\n    registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))])\n  );\n}\n"
  },
  {
    "path": "impl/runtime-core/src/helpers/toHandlers.ts",
    "content": "import { toHandlerKey } from \"@chibivue/shared\";\n\n/**\n * For prefixing keys in v-on=\"obj\" with \"on\"\n */\nexport function toHandlers(obj: Record<string, any>): Record<string, any> {\n  const ret: Record<string, any> = {};\n  for (const key in obj) {\n    ret[toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/index.ts",
    "content": "// Core API ------------------------------------------------------------------\n\nexport {\n  // core\n  reactive,\n  readonly,\n  shallowReactive,\n  ref,\n  shallowRef,\n  // utilities\n  unref,\n  proxyRefs,\n  isRef,\n  isReactive,\n  isReadonly,\n  isProxy,\n  toRaw,\n  markRaw,\n  triggerRef,\n  toRef,\n  toRefs,\n  customRef,\n  // effect\n  ReactiveEffect,\n  // effect scope\n  EffectScope,\n  effectScope,\n  effect,\n  getCurrentScope,\n  onScopeDispose,\n} from \"@chibivue/reactivity\";\nexport type {\n  Ref,\n  ShallowRef,\n  ReactiveFlags,\n  ComputedRef,\n  ToRef,\n  ToRefs,\n  ShallowUnwrapRef,\n  UnwrapRef,\n  ShallowReactive,\n  DeepReadonly,\n  UnwrapNestedRefs,\n  MaybeRef,\n  MaybeRefOrGetter,\n  CustomRefFactory,\n} from \"@chibivue/reactivity\";\n\nexport { computed } from \"./apiComputed\";\nexport {\n  onBeforeMount,\n  onMounted,\n  onBeforeUpdate,\n  onUpdated,\n  onBeforeUnmount,\n  onUnmounted,\n  onActivated,\n  onDeactivated,\n} from \"./apiLifecycle\";\nexport { provide, inject, hasInjectionContext, type InjectionKey } from \"./apiInject\";\nexport {\n  watch,\n  watchEffect,\n  type WatchOptions,\n  type WatchSource,\n  type WatchCallback,\n} from \"./apiWatch\";\n\nexport { h } from \"./h\";\n\nexport { resolveComponent } from \"./helpers/resolveAssets\";\nexport { renderList } from \"./helpers/renderList\";\n\nexport {\n  type VNode,\n  type VNodeProps,\n  type VNodeProps as VNodeData,\n  type VNodeArrayChildren,\n  createVNode,\n  createTextVNode,\n  createCommentVNode,\n  createElementVNode,\n  mergeProps,\n  normalizeVNode,\n  isVNode,\n  Fragment,\n  Text,\n  Comment,\n} from \"./vnode\";\n\nexport { Teleport, type TeleportProps } from \"./components/Teleport\";\nexport { KeepAlive, type KeepAliveProps } from \"./components/KeepAlive\";\n\nexport { type RendererOptions, type RootRenderFunction, createRenderer } from \"./renderer\";\nexport type { DirectiveBinding, DirectiveHook, ObjectDirective } from \"./directives\";\n\nexport { withDirectives } from \"./directives\";\n\nexport type { CreateAppFunction, App, AppConfig, AppContext, Plugin } from \"./apiCreateApp\";\nexport { createAppContext } from \"./apiCreateApp\";\nexport { defineComponent } from \"./apiDefineComponent\";\nexport {\n  defineAsyncComponent,\n  type AsyncComponentOptions,\n  type AsyncComponentLoader,\n} from \"./apiAsyncComponent\";\n\nexport {\n  type ComponentInternalInstance,\n  type Component,\n  type Data,\n  type LifecycleHook,\n  registerRuntimeCompiler,\n  getCurrentInstance,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nexport { LifecycleHooks } from \"./enums\";\nexport { type ComponentOptions, type RenderFunction } from \"./componentOptions\";\nexport { type ComponentPublicInstance } from \"./componentPublicInstance\";\n\nexport {\n  capitalize,\n  toHandlerKey,\n  toDisplayString,\n  normalizeClass,\n  normalizeStyle,\n  normalizeProps,\n  PatchFlags,\n  PatchFlagNames,\n} from \"@chibivue/shared\";\n\nexport { toHandlers } from \"./helpers/toHandlers\";\n"
  },
  {
    "path": "impl/runtime-core/src/renderer.ts",
    "content": "import {\n  type VaporComponentInternalInstance,\n  createVaporComponentInstance,\n  initialRenderVaporComponent,\n} from \"@chibivue/runtime-vapor\";\nimport { ReactiveEffect } from \"@chibivue/reactivity\";\nimport { ShapeFlags, invokeArrayFns, isFunction } from \"@chibivue/shared\";\n\nimport { createAppAPI } from \"./apiCreateApp\";\nimport {\n  type ComponentInternalInstance,\n  type Data,\n  createComponentInstance,\n  setupComponent,\n} from \"./component\";\nimport { updateProps } from \"./componentProps\";\nimport { updateSlots } from \"./componentSlots\";\nimport { renderComponentRoot } from \"./componentRenderUtils\";\nimport { invokeDirectiveHook } from \"./directives\";\nimport { setRef } from \"./rendererTemplateRef\";\nimport {\n  type SchedulerJob,\n  flushPostFlushCbs,\n  flushPreFlushCbs,\n  queueJob,\n  queuePostFlushCb,\n} from \"./scheduler\";\nimport {\n  Comment,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  isSameVNodeType,\n  normalizeVNode,\n} from \"./vnode\";\nimport {\n  isTeleport,\n  Teleport,\n  type TeleportInternals,\n  type TeleportVNode,\n} from \"./components/Teleport\";\nimport { isKeepAlive, type KeepAliveContext } from \"./components/KeepAlive\";\n\nexport type RootRenderFunction<HostElement = RendererElement> = (\n  vnode: VNode | null,\n  container: HostElement,\n  parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => void;\n\nexport interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {\n  patchProp(el: HostElement, key: string, prevValue: any, nextValue: any): void;\n\n  insert(parentNode: HostNode, newNode: HostNode, anchor?: HostNode | null): void;\n  remove(child: HostNode): void;\n\n  createElement(tagName: string): HostElement;\n  createComment(text: string): HostNode;\n  createText(text: string): Text;\n\n  setText(node: HostNode, text: string): void;\n  setElementText(node: HostElement, text: string): void;\n\n  parentNode(node: HostNode): HostNode | null;\n  nextSibling(node: HostNode): HostNode | null;\n\n  querySelector?(selector: string): HostElement | null;\n}\n\n// Renderer Node can technically be any object in the context of core renderer\n// logic - they are never directly operated on and always passed to the node op\n// functions provided via options, so the internal constraint is really just\n// a generic object.\nexport interface RendererNode {\n  [key: string]: any;\n}\n\nexport interface RendererElement extends RendererNode {}\n\n// These functions are created inside a closure and therefore their types cannot\n// be directly exported. In order to avoid maintaining function signatures in\n// two places, we declare them once here and use them inside the closure.\ntype PatchFn = (\n  n1: VNode | null, // null means this is a mount\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => void;\n\ntype ProcessVaporComponentFn = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => void;\n\ntype ProcessTextOrCommentFn = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n) => void;\n\ntype MountChildrenFn = (\n  children: VNodeArrayChildren,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => void;\n\ntype PatchChildrenFn = (\n  n1: VNode | null,\n  n2: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => void;\n\ntype MoveFn = (vnode: VNode, container: RendererElement, anchor: RendererNode | null) => void;\n\ntype MountComponentFn = (\n  initialVNode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n  parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n) => void;\n\ntype NextFn = (vnode: VNode) => RendererNode | null;\n\ntype UnmountFn = (vnode: VNode, parentComponent?: ComponentInternalInstance) => void;\ntype RemoveFn = (vnode: VNode) => void;\n\ntype UnmountChildrenFn = (children: VNode[]) => void;\n\ntype SetupRenderEffectFn = (\n  instance: ComponentInternalInstance,\n  initialVNode: VNode,\n  container: RendererElement,\n  anchor: RendererNode | null,\n) => void;\n\nexport interface Renderer {\n  render: RootRenderFunction;\n  createApp: ReturnType<typeof createAppAPI>;\n}\n\n// implementation\nexport function createRenderer(options: RendererOptions): Renderer {\n  const {\n    insert: hostInsert,\n    remove: hostRemove,\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    createComment: hostCreateComment,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n  } = options;\n\n  const patch: PatchFn = (n1, n2, container, anchor, parentComponent = null) => {\n    if (n1 === n2) {\n      return;\n    }\n\n    // patching & not same type, unmount old tree\n    if (n1 && !isSameVNodeType(n1, n2)) {\n      // If the old vnode will be kept alive, its element will be moved to storage,\n      // so we can't use it or its siblings as anchor. Use the passed anchor instead.\n      const willBeKeptAlive = n1.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n      if (!willBeKeptAlive) {\n        anchor = getNextHostNode(n1);\n      }\n      unmount(n1, parentComponent as ComponentInternalInstance);\n      n1 = null;\n    }\n\n    const { type, ref, shapeFlag } = n2;\n    if (isFunction(type)) {\n      processVaporComponent(n1, n2, container, anchor, parentComponent);\n    } else if (type === Text) {\n      processText(n1, n2, container, anchor);\n    } else if (type === Comment) {\n      processCommentNode(n1, n2, container, anchor);\n    } else if (type === Fragment) {\n      processFragment(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.TELEPORT) {\n      (type as typeof Teleport).process(\n        n1 as TeleportVNode,\n        n2 as TeleportVNode,\n        container,\n        anchor,\n        parentComponent as ComponentInternalInstance,\n        internals,\n      );\n    } else if (shapeFlag & ShapeFlags.ELEMENT) {\n      processElement(n1, n2, container, anchor, parentComponent);\n    } else if (shapeFlag & ShapeFlags.COMPONENT) {\n      processComponent(n1, n2, container, anchor, parentComponent);\n    }\n\n    if (ref) {\n      setRef(ref, n2);\n    }\n  };\n\n  const processVaporComponent: ProcessVaporComponentFn = (\n    n1,\n    n2,\n    container,\n    anchor,\n    parentComponent,\n  ) => {\n    if (n1 == null) {\n      mountVaporComponent(n2, container, anchor, parentComponent);\n    } else {\n      // do nothing\n      // reactivity patch\n    }\n  };\n\n  const mountVaporComponent = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n  ) => {\n    const instance = createVaporComponentInstance(vnode, parentComponent);\n    const el = (vnode.el = initialRenderVaporComponent(instance));\n    const { bm, m } = instance;\n    if (bm) invokeArrayFns(bm);\n    hostInsert(el, container, anchor);\n    instance.isMounted = true;\n    if (m) queuePostFlushCb(m);\n  };\n\n  const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateText(n2.children as string)), container, anchor!);\n    } else {\n      const el = (n2.el = n1.el!);\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children as string);\n      }\n    }\n  };\n\n  const processCommentNode = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererElement | null,\n  ) => {\n    if (n1 == null) {\n      hostInsert((n2.el = hostCreateComment((n2.children as string) || \"\")), container, anchor);\n    } else {\n      n2.el = n1.el;\n    }\n  };\n\n  const processElement = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n  ) => {\n    if (n1 == null) {\n      mountElement(n2, container, anchor, parentComponent);\n    } else {\n      patchElement(n1, n2, parentComponent);\n    }\n  };\n\n  const mountElement = (\n    vnode: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n  ) => {\n    let el: RendererElement;\n    const { type, props, shapeFlag, dirs } = vnode;\n\n    el = vnode.el = hostCreateElement(type as string);\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      hostSetElementText(el, vnode.children as string);\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      mountChildren(vnode.children as VNodeArrayChildren, el, null, parentComponent);\n    }\n\n    dirs && invokeDirectiveHook(vnode, null, \"created\");\n\n    if (props) {\n      for (const key in props) {\n        hostPatchProp(el, key, null, props[key]);\n      }\n    }\n\n    dirs && invokeDirectiveHook(vnode, null, \"beforeMount\");\n    hostInsert(el, container, anchor!);\n    dirs && invokeDirectiveHook(vnode, null, \"mounted\");\n  };\n\n  const patchElement = (\n    n1: VNode,\n    n2: VNode,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n  ) => {\n    const el = (n2.el = n1.el!);\n    const { dirs } = n2;\n\n    const oldProps = n1.props ?? {};\n    const newProps = n2.props ?? {};\n\n    dirs && invokeDirectiveHook(n2, n1, \"beforeUpdate\");\n    patchChildren(n1, n2, el, null, parentComponent);\n    patchProps(el, oldProps, newProps);\n    dirs && invokeDirectiveHook(n2, n1, \"updated\");\n  };\n\n  const processComponent = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null = null,\n  ) => {\n    if (n1 == null) {\n      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {\n        (parentComponent as KeepAliveContext).activate(\n          n2,\n          container,\n          anchor,\n          parentComponent as ComponentInternalInstance,\n        );\n      } else {\n        mountComponent(n2, container, anchor, parentComponent);\n      }\n    } else {\n      updateComponent(n1, n2);\n    }\n  };\n\n  const patchProps = (el: RendererElement, oldProps: Data, newProps: Data) => {\n    for (const key in oldProps) {\n      if (!(key in newProps)) {\n        hostPatchProp(el, key, oldProps[key], null);\n      }\n    }\n    for (const key in newProps) {\n      const next = newProps[key];\n      const prev = oldProps[key];\n      // defer patching value\n      if (next !== prev) {\n        hostPatchProp(el, key, prev, next);\n      }\n    }\n  };\n\n  const processFragment = (\n    n1: VNode | null,\n    n2: VNode,\n    container: RendererElement,\n    anchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n  ) => {\n    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(\"\"))!;\n    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(\"\"))!;\n\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(\n        n2.children as VNodeArrayChildren,\n        container,\n        fragmentEndAnchor,\n        parentComponent,\n      );\n    } else {\n      patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent);\n    }\n  };\n\n  const mountChildren: MountChildrenFn = (children, container, anchor, parentComponent) => {\n    for (let i = 0; i < children.length; i++) {\n      const child = (children[i] = normalizeVNode(children[i]));\n      patch(null, child, container, anchor, parentComponent);\n    }\n  };\n\n  const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {\n    // prettier-ignore\n    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));\n\n    if (isKeepAlive(initialVNode)) {\n      (instance as KeepAliveContext).renderer = {\n        p: patch,\n        m: move,\n        um: unmount,\n        o: options,\n      };\n    }\n\n    setupComponent(instance);\n    setupRenderEffect(instance, initialVNode, container, anchor);\n  };\n\n  const updateComponent = (n1: VNode, n2: VNode) => {\n    const instance = (n2.component = n1.component)!;\n    instance.next = n2;\n    instance.update();\n  };\n\n  const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor) => {\n    const componentUpdateFn = () => {\n      const { bm, m, bu, u } = instance;\n\n      if (!instance.isMounted) {\n        // beforeMount hook\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n\n        const subTree = (instance.subTree = renderComponentRoot(instance));\n        patch(null, subTree, container, anchor, instance);\n        initialVNode.el = subTree.el;\n        instance.isMounted = true;\n\n        // mounted hook\n        if (m) {\n          queuePostFlushCb(m);\n        }\n      } else {\n        let { next, vnode } = instance;\n\n        // beforeUpdate hook\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n\n        if (next) {\n          next.el = vnode.el;\n          updateComponentPreRender(instance, next);\n        } else {\n          next = vnode;\n        }\n\n        const prevTree = instance.subTree;\n        const nextTree = renderComponentRoot(instance);\n        instance.subTree = nextTree;\n\n        // If prevTree will be kept alive, its element will be moved to storage,\n        // so we can't use it as anchor reference. Use null instead.\n        const prevTreeWillBeKeptAlive = prevTree.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;\n        const anchor = prevTreeWillBeKeptAlive ? null : getNextHostNode(prevTree);\n\n        patch(prevTree, nextTree, hostParentNode(prevTree.el!)!, anchor, instance);\n        next.el = nextTree.el;\n\n        // updated hook\n        if (u) {\n          queuePostFlushCb(u);\n        }\n      }\n    };\n    const effect = (instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      () => queueJob(update),\n      instance.scope,\n    ));\n    const update: SchedulerJob = (instance.update = () => effect.run());\n    update.id = instance.uid;\n\n    update();\n  };\n\n  const updateComponentPreRender = (instance: ComponentInternalInstance, nextVNode: VNode) => {\n    nextVNode.component = instance;\n    instance.vnode = nextVNode;\n    instance.next = null;\n    updateProps(instance, nextVNode.props);\n    updateSlots(instance, nextVNode.children);\n    flushPreFlushCbs();\n  };\n\n  const patchChildren: PatchChildrenFn = (n1, n2, container, anchor, parentComponent) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { shapeFlag } = n2;\n\n    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        unmountChildren(c1 as VNode[]);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2 as string);\n      }\n    } else {\n      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          patchKeyedChildren(c1 as VNode[], c2 as VNode[], container, anchor, parentComponent);\n        } else {\n          // no new children, just unmount old\n          unmountChildren(c1 as VNode[]);\n        }\n      } else {\n        // prev children was text OR null\n        // new children is array OR null\n        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {\n          hostSetElementText(container, \"\");\n        }\n        // mount new if array\n        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n          mountChildren(c2 as VNodeArrayChildren, container, anchor, parentComponent);\n        }\n      }\n    }\n  };\n\n  // can be all-keyed or mixed\n  const patchKeyedChildren = (\n    c1: VNode[],\n    c2: VNode[],\n    container: RendererElement,\n    parentAnchor: RendererNode | null,\n    parentComponent: ComponentInternalInstance | VaporComponentInternalInstance | null,\n  ) => {\n    let i = 0;\n    const l2 = c2.length;\n    let e1 = c1.length - 1; // prev ending index\n    let e2 = l2 - 1; // next ending index\n\n    // 1. sync from start\n    // (a b) c\n    // (a b) d e\n    while (i <= e1 && i <= e2) {\n      const n1 = c1[i];\n      const n2 = (c2[i] = normalizeVNode(c2[i]));\n      if (isSameVNodeType(n1, n2)) {\n        patch(n1, n2, container, null, parentComponent);\n      } else {\n        break;\n      }\n      i++;\n    }\n\n    // 2. sync from end\n    // a (b c)\n    // d e (b c)\n    while (i <= e1 && i <= e2) {\n      const n1 = c1[e1];\n      const n2 = (c2[i] = normalizeVNode(c2[i]));\n      if (isSameVNodeType(n1, n2)) {\n        patch(n1, n2, container, null, parentComponent);\n      } else {\n        break;\n      }\n      e1--;\n      e2--;\n    }\n\n    // 3. common sequence + mount\n    // (a b)\n    // (a b) c\n    // i = 2, e1 = 1, e2 = 2\n    // (a b)\n    // c (a b)\n    // i = 0, e1 = -1, e2 = 0\n    if (i > e1) {\n      if (i <= e2) {\n        const nextPos = e2 + 1;\n        const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor;\n        while (i <= e2) {\n          patch(null, (c2[i] = normalizeVNode(c2[i])), container, anchor, parentComponent);\n          i++;\n        }\n      }\n    }\n\n    // 4. common sequence + unmount\n    // (a b) c\n    // (a b)\n    // i = 2, e1 = 2, e2 = 1\n    // a (b c)\n    // (b c)\n    // i = 0, e1 = 0, e2 = -1\n    else if (i > e2) {\n      while (i <= e1) {\n        unmount(c1[i]);\n        i++;\n      }\n    }\n\n    // 5. unknown sequence\n    // [i ... e1 + 1]: a b [c d e] f g\n    // [i ... e2 + 1]: a b [e d c h] f g\n    // i = 2, e1 = 4, e2 = 5\n    else {\n      const s1 = i; // prev starting index\n      const s2 = i; // next starting index\n\n      // 5.1 build key:index map for newChildren\n      const keyToNewIndexMap: Map<string | number | symbol, number> = new Map();\n      for (i = s2; i <= e2; i++) {\n        const nextChild = (c2[i] = normalizeVNode(c2[i]));\n        if (nextChild.key != null) {\n          keyToNewIndexMap.set(nextChild.key, i);\n        }\n      }\n\n      // 5.2 loop through old children left to be patched and try to patch\n      // matching nodes & remove nodes that are no longer present\n      let j;\n      let patched = 0;\n      const toBePatched = e2 - s2 + 1;\n      let moved = false;\n      // used to track whether any node has moved\n      let maxNewIndexSoFar = 0;\n      // works as Map<newIndex, oldIndex>\n      // Note that oldIndex is offset by +1\n      // and oldIndex = 0 is a special value indicating the new node has\n      // no corresponding old node.\n      // used for determining longest stable subsequence\n      const newIndexToOldIndexMap = new Array(toBePatched);\n      for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n\n      for (i = s1; i <= e1; i++) {\n        const prevChild = c1[i];\n        if (patched >= toBePatched) {\n          // all new children have been patched so this can only be a removal\n          unmount(prevChild);\n          continue;\n        }\n        let newIndex;\n        if (prevChild.key != null) {\n          newIndex = keyToNewIndexMap.get(prevChild.key);\n        } else {\n          // key-less node, try to locate a key-less node of the same type\n          for (j = s2; j <= e2; j++) {\n            if (newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)) {\n              newIndex = j;\n              break;\n            }\n          }\n        }\n        if (newIndex === undefined) {\n          unmount(prevChild);\n        } else {\n          newIndexToOldIndexMap[newIndex - s2] = i + 1;\n          if (newIndex >= maxNewIndexSoFar) {\n            maxNewIndexSoFar = newIndex;\n          } else {\n            moved = true;\n          }\n          patch(prevChild, c2[newIndex] as VNode, container, null, parentComponent);\n          patched++;\n        }\n      }\n\n      // 5.3 move and mount\n      // generate longest stable subsequence only when nodes have moved\n      const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];\n      j = increasingNewIndexSequence.length - 1;\n      // looping backwards so that we can use last patched node as anchor\n      for (i = toBePatched - 1; i >= 0; i--) {\n        const nextIndex = s2 + i;\n        const nextChild = c2[nextIndex] as VNode;\n        const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor;\n        if (newIndexToOldIndexMap[i] === 0) {\n          // mount new\n          patch(null, nextChild, container, anchor, parentComponent);\n        } else if (moved) {\n          // move if:\n          // There is no stable subsequence (e.g. a reverse)\n          // OR current node is not among the stable sequence\n          if (j < 0 || i !== increasingNewIndexSequence[j]) {\n            move(nextChild, container, anchor);\n          } else {\n            j--;\n          }\n        }\n      }\n    }\n  };\n\n  const move: MoveFn = (vnode, container, anchor) => {\n    const { type, children, el, shapeFlag } = vnode;\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      move(vnode.component!.subTree, container, anchor);\n      return;\n    }\n\n    if (shapeFlag & ShapeFlags.TELEPORT) {\n      (type as typeof Teleport).move(vnode as TeleportVNode, container, anchor, internals);\n      return;\n    }\n\n    if (type === Fragment) {\n      hostInsert(el!, container, anchor);\n      for (let i = 0; i < (children as VNode[]).length; i++) {\n        move((children as VNode[])[i], container, anchor);\n      }\n      hostInsert(vnode.anchor!, container, anchor);\n      return;\n    }\n\n    hostInsert(el!, container, anchor!);\n  };\n\n  const unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {\n    const { type, shapeFlag, children } = vnode;\n\n    if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {\n      (parentComponent as KeepAliveContext).deactivate(vnode);\n      return;\n    }\n\n    if (shapeFlag & ShapeFlags.COMPONENT) {\n      unmountComponent(vnode.component!);\n    } else if (shapeFlag & ShapeFlags.TELEPORT) {\n      (type as typeof Teleport).remove(vnode as TeleportVNode, internals);\n      return;\n    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n      unmountChildren(children as VNode[]);\n    }\n    remove(vnode);\n  };\n\n  const remove: RemoveFn = (vnode) => {\n    const { el, type, anchor } = vnode;\n    if (type === Fragment) {\n      removeFragment(el!, anchor!);\n    }\n\n    hostRemove(el!);\n  };\n\n  const removeFragment = (cur: RendererNode, end: RendererNode) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur)!;\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n\n  const unmountComponent = (instance: ComponentInternalInstance) => {\n    const { subTree, scope, bum, um } = instance;\n\n    // beforeUnmount hook\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n\n    scope.stop();\n    unmount(subTree);\n\n    // unmounted hook\n    if (um) {\n      queuePostFlushCb(um);\n    }\n  };\n\n  const unmountChildren: UnmountChildrenFn = (children) => {\n    for (let i = 0; i < children.length; i++) {\n      unmount(children[i]);\n    }\n  };\n\n  const getNextHostNode: NextFn = (vnode) => {\n    if (vnode.shapeFlag & ShapeFlags.COMPONENT) {\n      return getNextHostNode(vnode.component!.subTree);\n    }\n    return hostNextSibling(vnode.el!);\n  };\n\n  const internals: TeleportInternals = {\n    mc: mountChildren,\n    pc: patchChildren,\n    pbc: patchChildren,\n    um: unmount,\n    m: move,\n    o: options,\n  };\n\n  const render: RootRenderFunction = (vnode, container, parent) => {\n    if (vnode === null) {\n      if (container._vnode) {\n        unmount(container._vnode);\n      }\n    } else {\n      patch((container as any)._vnode || null, vnode, container, null, parent);\n    }\n    flushPreFlushCbs();\n    flushPostFlushCbs();\n    (container as any)._vnode = vnode;\n  };\n\n  return {\n    render: render,\n    createApp: createAppAPI(render),\n  };\n}\n\n// https://en.wikipedia.org/wiki/Longest_increasing_subsequence\nfunction getSequence(arr: number[]): number[] {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = (u + v) >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\n"
  },
  {
    "path": "impl/runtime-core/src/rendererTemplateRef.ts",
    "content": "import type { Ref } from \"@chibivue/reactivity\";\nimport { ShapeFlags } from \"@chibivue/shared\";\nimport type { VNode } from \"./vnode\";\n\nexport function setRef(rawRef: Ref, vnode: VNode): void {\n  const { shapeFlag } = vnode;\n  if (shapeFlag & ShapeFlags.COMPONENT) {\n    rawRef.value = vnode.component?.setupState; // TODO: proxy\n  } else if (shapeFlag & ShapeFlags.ELEMENT) {\n    rawRef.value = vnode.el;\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/scheduler.ts",
    "content": "import { isArray } from \"@chibivue/shared\";\n\nexport interface SchedulerJob extends Function {\n  id?: number;\n  pre?: boolean;\n  active?: boolean;\n}\n\nexport type SchedulerJobs = SchedulerJob | SchedulerJob[];\n\nlet isFlushing = false;\nlet isFlushPending = false;\n\nconst queue: SchedulerJob[] = [];\nlet flushIndex = 0;\n\nconst pendingPostFlushCbs: SchedulerJob[] = [];\nlet activePostFlushCbs: SchedulerJob[] | null = null;\nlet postFlushIndex = 0;\n\nconst resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>;\nlet currentFlushPromise: Promise<void> | null = null;\n\nexport function nextTick<T = void>(this: T, fn?: (this: T) => void): Promise<void> {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\n\nfunction findInsertionIndex(id: number) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n\n  while (start < end) {\n    const middle = (start + end) >>> 1;\n    const middleJobId = getId(queue[middle]);\n    middleJobId < id ? (start = middle + 1) : (end = middle);\n  }\n\n  return start;\n}\n\nexport function queueJob(job: SchedulerJob): void {\n  if (!queue.length || !queue.includes(job, isFlushing ? flushIndex + 1 : flushIndex)) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\n\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(flushJobs);\n  }\n}\n\nexport function queuePostFlushCb(cb: SchedulerJobs): void {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, postFlushIndex)) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\n\nexport function flushPreFlushCbs(i: number = isFlushing ? flushIndex + 1 : 0): void {\n  for (; i < queue.length; i++) {\n    const cb = queue[i];\n    if (cb && cb.pre) {\n      queue.splice(i, 1);\n      i--;\n      cb();\n    }\n  }\n}\n\nexport function flushPostFlushCbs(): void {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)];\n    pendingPostFlushCbs.length = 0;\n\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n\n    activePostFlushCbs = deduped;\n\n    activePostFlushCbs.sort((a, b) => getId(a) - getId(b));\n\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      activePostFlushCbs[postFlushIndex]();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\n\nconst getId = (job: SchedulerJob): number => (job.id == null ? Infinity : job.id);\n\nconst comparator = (a: SchedulerJob, b: SchedulerJob): number => {\n  const diff = getId(a) - getId(b);\n  if (diff === 0) {\n    if (a.pre && !b.pre) return -1;\n    if (b.pre && !a.pre) return 1;\n  }\n  return diff;\n};\n\nfunction flushJobs() {\n  isFlushPending = false;\n  isFlushing = true;\n  queue.sort(comparator);\n\n  try {\n    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {\n      const job = queue[flushIndex];\n      if (job && job.active !== false) {\n        job();\n      }\n    }\n  } finally {\n    flushIndex = 0;\n    queue.length = 0;\n    flushPostFlushCbs();\n    isFlushing = false;\n    currentFlushPromise = null;\n    if (queue.length || pendingPostFlushCbs.length) {\n      flushJobs();\n    }\n  }\n}\n"
  },
  {
    "path": "impl/runtime-core/src/vnode.ts",
    "content": "import type { Ref } from \"@chibivue/reactivity\";\nimport type { VaporComponent } from \"@chibivue/runtime-vapor\";\nimport { ShapeFlags } from \"@chibivue/shared\";\n\nimport {\n  isArray,\n  isFunction,\n  isObject,\n  isString,\n  normalizeClass,\n  normalizeStyle,\n} from \"@chibivue/shared\";\nimport { currentRenderingInstance } from \"./componentRenderContext\";\nimport type { Component, ComponentInternalInstance, Data } from \"./component\";\nimport type { ComponentPublicInstance } from \"./componentPublicInstance\";\nimport type { AppContext } from \"./apiCreateApp\";\nimport type { DirectiveBinding } from \"./directives\";\nimport type { RawSlots } from \"./componentSlots\";\nimport type { TeleportProps } from \"./components/Teleport\";\nimport { isTeleport } from \"./components/Teleport\";\n\nexport type VNodeTypes =\n  | string\n  | typeof Text\n  | typeof Comment\n  | typeof Fragment\n  | typeof TeleportSymbol\n  | Component\n  | VaporComponent;\n\nexport const Text: unique symbol = Symbol();\nexport const Comment: unique symbol = Symbol();\nexport const Fragment = Symbol() as any as {\n  __isFragment: true;\n  new (): {\n    $props: VNodeProps;\n  };\n};\nexport const TeleportSymbol = Symbol() as any as {\n  __isTeleport: true;\n  new (): {\n    $props: TeleportProps;\n  };\n};\n\nexport interface VNode<HostNode = any> {\n  __v_isVNode: true;\n  type: VNodeTypes;\n  props: VNodeProps | null;\n  key: string | number | symbol | null;\n  ref: Ref | null;\n\n  // DOM\n  el: HostNode | undefined;\n  anchor: HostNode | null; // fragment anchor\n\n  children: VNodeNormalizedChildren;\n  component: ComponentInternalInstance | null;\n  dirs: DirectiveBinding[] | null;\n  ctx: ComponentPublicInstance | null;\n  shapeFlag: number;\n\n  // optimization only\n  patchFlag: number;\n  dynamicProps: string[] | null;\n  dynamicChildren: VNode[] | null;\n\n  // application root node only\n  appContext: AppContext | null;\n\n  // teleport\n  target: HostNode | null;\n  targetAnchor: HostNode | null;\n\n  // transition\n  transition: any | null;\n}\n\nexport interface VNodeProps {\n  [key: string]: any;\n}\n\nexport type VNodeNormalizedChildren = string | VNodeArrayChildren | RawSlots;\n\nexport type VNodeChild = VNodeChildAtom | VNodeArrayChildren;\nexport type VNodeArrayChildren = Array<VNodeArrayChildren | VNodeChildAtom>;\ntype VNodeChildAtom = VNode | string;\n\nexport function isVNode(value: unknown): value is VNode {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    \"__v_isVNode\" in value &&\n    value.__v_isVNode === true\n  );\n}\n\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  return n1.type === n2.type && n1.key === n2.key;\n}\n\nexport const createVNode = (\n  type: VNodeTypes,\n  props: VNodeProps | null = null,\n  children: unknown = null,\n  patchFlag: number = 0,\n  dynamicProps: string[] | null = null,\n): VNode => {\n  const shapeFlag = isString(type)\n    ? ShapeFlags.ELEMENT\n    : isTeleport(type)\n      ? ShapeFlags.TELEPORT\n      : isObject(type)\n        ? ShapeFlags.COMPONENT\n        : 0;\n\n  return createBaseVNode(type, props, children, shapeFlag, patchFlag, dynamicProps);\n};\n\nfunction createBaseVNode(\n  type: VNodeTypes,\n  props: VNodeProps | null,\n  children: unknown = null,\n  shapeFlag: ShapeFlags | 0 = ShapeFlags.ELEMENT,\n  patchFlag: number = 0,\n  dynamicProps: string[] | null = null,\n): VNode {\n  const vnode = {\n    __v_isVNode: true,\n    type,\n    props,\n    key: props && props.key,\n    ref: props?.ref ?? null,\n    children,\n    el: null,\n    anchor: null,\n    ctx: currentRenderingInstance,\n    shapeFlag,\n    patchFlag,\n    dynamicProps,\n    dynamicChildren: null,\n    component: null,\n    dirs: null,\n    appContext: null,\n    target: null,\n    targetAnchor: null,\n    transition: null,\n  } as VNode;\n\n  normalizeChildren(vnode, children);\n\n  if (children) {\n    vnode.shapeFlag |= isString(children) ? ShapeFlags.TEXT_CHILDREN : ShapeFlags.ARRAY_CHILDREN;\n  }\n\n  return vnode;\n}\n\nexport { createVNode as createElementVNode };\n\nexport function createCommentVNode(text: string = \"\"): VNode {\n  return createVNode(Comment, null, text);\n}\n\nexport function normalizeChildren(vnode: VNode, children: unknown): void {\n  let type = 0;\n  const { shapeFlag } = vnode;\n\n  if (children == null) {\n    children = null;\n  } else if (isFunction(children)) {\n    children = { default: children };\n    type = ShapeFlags.SLOTS_CHILDREN;\n  } else if (isArray(children)) {\n    type = ShapeFlags.ARRAY_CHILDREN;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & ShapeFlags.ELEMENT) {\n      return;\n    } else {\n      type = ShapeFlags.SLOTS_CHILDREN;\n    }\n  } else {\n    children = String(children);\n    type = ShapeFlags.TEXT_CHILDREN;\n  }\n  vnode.children = children as VNodeNormalizedChildren;\n  vnode.shapeFlag |= type;\n}\n\nexport function createTextVNode(text: string = \" \"): VNode {\n  return createVNode(Text, null, text);\n}\n\nexport function normalizeVNode(child: VNodeChild): VNode {\n  if (typeof child === \"object\") {\n    return cloneIfMounted(child as VNode);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\n\nexport function cloneIfMounted(child: VNode): VNode {\n  return child.el === null ? child : cloneVNode(child);\n}\n\nexport function cloneVNode<T>(vnode: VNode<T>): VNode<T> {\n  const { props, children } = vnode;\n  const cloned: VNode<T> = {\n    __v_isVNode: true,\n    type: vnode.type,\n    props,\n    key: vnode.key,\n    ref: vnode.ref,\n    children: isArray(children) ? (children as VNode[]).map(cloneVNode) : children,\n    component: vnode.component,\n    dirs: vnode.dirs,\n    shapeFlag: vnode.shapeFlag,\n    patchFlag: vnode.patchFlag,\n    dynamicProps: vnode.dynamicProps,\n    dynamicChildren: vnode.dynamicChildren,\n    el: vnode.el,\n    anchor: vnode.anchor,\n    ctx: vnode.ctx,\n    appContext: vnode.appContext,\n    target: vnode.target,\n    targetAnchor: vnode.targetAnchor,\n    transition: vnode.transition,\n  };\n  return cloned;\n}\n\nexport function mergeProps(...args: (Data & VNodeProps)[]): Data {\n  const ret: Data = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      } /*if (isOn(key))*/ else {\n        // TODO: v-on=\"object\"\n      }\n    }\n  }\n  return ret;\n}\n"
  },
  {
    "path": "impl/runtime-dom/package.json",
    "content": "{\n  \"name\": \"@chibivue/runtime-dom\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/runtime-dom/src/components/Transition.ts",
    "content": "import type { VNode } from \"@chibivue/runtime-core\";\n\nexport interface TransitionProps {\n  name?: string;\n  type?: \"transition\" | \"animation\";\n  css?: boolean;\n  duration?: number | { enter: number; leave: number };\n  enterFromClass?: string;\n  enterActiveClass?: string;\n  enterToClass?: string;\n  appearFromClass?: string;\n  appearActiveClass?: string;\n  appearToClass?: string;\n  leaveFromClass?: string;\n  leaveActiveClass?: string;\n  leaveToClass?: string;\n  mode?: \"in-out\" | \"out-in\" | \"default\";\n  appear?: boolean;\n  onBeforeEnter?: (el: Element) => void;\n  onEnter?: (el: Element, done: () => void) => void;\n  onAfterEnter?: (el: Element) => void;\n  onEnterCancelled?: (el: Element) => void;\n  onBeforeLeave?: (el: Element) => void;\n  onLeave?: (el: Element, done: () => void) => void;\n  onAfterLeave?: (el: Element) => void;\n  onLeaveCancelled?: (el: Element) => void;\n  onBeforeAppear?: (el: Element) => void;\n  onAppear?: (el: Element, done: () => void) => void;\n  onAfterAppear?: (el: Element) => void;\n  onAppearCancelled?: (el: Element) => void;\n}\n\nexport interface TransitionHooks<HostElement = Element> {\n  mode: string;\n  beforeEnter(el: HostElement): void;\n  enter(el: HostElement, done: () => void): void;\n  leave(el: HostElement, remove: () => void): void;\n  clone(vnode: VNode): TransitionHooks<HostElement>;\n}\n\nconst TRANSITION = \"transition\";\nconst ANIMATION = \"animation\";\n\nexport interface ElementWithTransition extends HTMLElement {\n  _vtc?: Set<string>;\n}\n\nexport function resolveTransitionProps(\n  rawProps: TransitionProps,\n): TransitionProps & TransitionHooks {\n  const {\n    name = \"v\",\n    type,\n    css = true,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    appearFromClass = enterFromClass,\n    appearActiveClass = enterActiveClass,\n    appearToClass = enterToClass,\n    leaveFromClass = `${name}-leave-from`,\n    leaveActiveClass = `${name}-leave-active`,\n    leaveToClass = `${name}-leave-to`,\n    mode = \"default\",\n    appear = false,\n    onBeforeEnter,\n    onEnter,\n    onAfterEnter,\n    onEnterCancelled,\n    onBeforeLeave,\n    onLeave,\n    onAfterLeave,\n    onLeaveCancelled,\n    onBeforeAppear = onBeforeEnter,\n    onAppear = onEnter,\n    onAfterAppear = onAfterEnter,\n    onAppearCancelled = onEnterCancelled,\n  } = rawProps;\n\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n\n  const finishEnter = (\n    el: Element & ElementWithTransition,\n    isAppear: boolean,\n    done?: () => void,\n  ) => {\n    removeTransitionClass(el, isAppear ? appearToClass : enterToClass);\n    removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);\n    done && done();\n  };\n\n  const finishLeave = (el: Element & ElementWithTransition, done?: () => void) => {\n    removeTransitionClass(el, leaveToClass);\n    removeTransitionClass(el, leaveActiveClass);\n    done && done();\n  };\n\n  const makeEnterHook = (isAppear: boolean) => {\n    return (el: Element, done: () => void) => {\n      const _el = el as Element & ElementWithTransition;\n      const hook = isAppear ? onAppear : onEnter;\n      const resolve = () => finishEnter(_el, isAppear, done);\n      callHook(hook, [el, resolve]);\n      nextFrame(() => {\n        removeTransitionClass(_el, isAppear ? appearFromClass : enterFromClass);\n        addTransitionClass(_el, isAppear ? appearToClass : enterToClass);\n        if (!hasExplicitCallback(hook)) {\n          whenTransitionEnds(_el, type, enterDuration, resolve);\n        }\n      });\n    };\n  };\n\n  return {\n    ...rawProps,\n    mode,\n    beforeEnter(el) {\n      const _el = el as Element & ElementWithTransition;\n      callHook(onBeforeEnter, [el]);\n      addTransitionClass(_el, enterFromClass);\n      addTransitionClass(_el, enterActiveClass);\n    },\n    enter: makeEnterHook(false),\n    leave(el, done) {\n      const _el = el as Element & ElementWithTransition;\n      const resolve = () => finishLeave(_el, done);\n      addTransitionClass(_el, leaveFromClass);\n      // force reflow\n      forceReflow();\n      addTransitionClass(_el, leaveActiveClass);\n      nextFrame(() => {\n        removeTransitionClass(_el, leaveFromClass);\n        addTransitionClass(_el, leaveToClass);\n        if (!hasExplicitCallback(onLeave)) {\n          whenTransitionEnds(_el, type, leaveDuration, resolve);\n        }\n      });\n      callHook(onLeave, [el, resolve]);\n    },\n    clone(vnode) {\n      return resolveTransitionProps(rawProps);\n    },\n  };\n}\n\nfunction normalizeDuration(duration: TransitionProps[\"duration\"]): [number, number] | null {\n  if (duration == null) {\n    return null;\n  } else if (typeof duration === \"object\") {\n    return [NumberOf(duration.enter), NumberOf(duration.leave)];\n  } else {\n    const n = NumberOf(duration);\n    return [n, n];\n  }\n}\n\nfunction NumberOf(val: unknown): number {\n  const res = Number(val);\n  return isNaN(res) ? 0 : res;\n}\n\nexport function addTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el._vtc || (el._vtc = new Set())).add(cls);\n}\n\nexport function removeTransitionClass(el: Element & ElementWithTransition, cls: string): void {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const { _vtc } = el;\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el._vtc = undefined;\n    }\n  }\n}\n\nfunction nextFrame(cb: () => void): void {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\n\nfunction callHook(\n  hook: ((el: Element, done: () => void) => void) | ((el: Element) => void) | undefined,\n  args: any[],\n): void {\n  if (hook) {\n    hook(...(args as [any, any]));\n  }\n}\n\nfunction hasExplicitCallback(hook: ((el: Element, done: () => void) => void) | undefined): boolean {\n  return hook ? hook.length > 1 : false;\n}\n\ninterface CSSTransitionInfo {\n  type: typeof TRANSITION | typeof ANIMATION | null;\n  propCount: number;\n  timeout: number;\n  hasTransform: boolean;\n}\n\nexport function getTransitionInfo(\n  el: Element,\n  expectedType?: TransitionProps[\"type\"],\n): CSSTransitionInfo {\n  const styles = window.getComputedStyle(el) as CSSStyleDeclaration & {\n    webkitTransitionDuration?: string;\n    webkitTransitionDelay?: string;\n    webkitAnimationDuration?: string;\n    webkitAnimationDelay?: string;\n  };\n  const getStyleProperties = (key: keyof CSSStyleDeclaration) => (styles[key] || \"\") as string;\n  const transitionDelays = getStyleProperties(\"transitionDelay\").split(\", \");\n  const transitionDurations = getStyleProperties(\"transitionDuration\").split(\", \");\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n  const animationDelays = getStyleProperties(\"animationDelay\").split(\", \");\n  const animationDurations = getStyleProperties(\"animationDuration\").split(\", \");\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n\n  let type: CSSTransitionInfo[\"type\"] = null;\n  let timeout = 0;\n  let propCount = 0;\n\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION;\n      timeout = animationTimeout;\n      propCount = animationDurations.length;\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null;\n    propCount = type\n      ? type === TRANSITION\n        ? transitionDurations.length\n        : animationDurations.length\n      : 0;\n  }\n\n  const hasTransform =\n    type === TRANSITION && /\\b(transform|all)(,|$)/.test(getStyleProperties(\"transitionProperty\"));\n\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform,\n  };\n}\n\nfunction getTimeout(delays: string[], durations: string[]): number {\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays);\n  }\n  return Math.max(...durations.map((d, i) => toMs(d) + toMs(delays[i])));\n}\n\nfunction toMs(s: string): number {\n  return Number(s.slice(0, -1).replace(\",\", \".\")) * 1000;\n}\n\nexport function whenTransitionEnds(\n  el: Element & { _endId?: number },\n  expectedType: TransitionProps[\"type\"] | undefined,\n  explicitTimeout: number | null,\n  resolve: () => void,\n): void {\n  const id = (el._endId = ++endId);\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n\n  if (explicitTimeout) {\n    setTimeout(resolveIfNotStale, explicitTimeout);\n    return;\n  }\n\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n\n  const endEvent = type + \"end\";\n  let ended = 0;\n\n  const end = () => {\n    el.removeEventListener(endEvent, onEnd);\n    resolveIfNotStale();\n  };\n\n  const onEnd = (e: Event) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n\n  el.addEventListener(endEvent, onEnd);\n}\n\nlet endId = 0;\n\nexport function forceReflow(): number {\n  return document.body.offsetHeight;\n}\n\nexport const TransitionPropsValidators: Record<string, any> = {\n  name: String,\n  type: String,\n  css: {\n    type: Boolean,\n    default: true,\n  },\n  duration: [String, Number, Object],\n  enterFromClass: String,\n  enterActiveClass: String,\n  enterToClass: String,\n  appearFromClass: String,\n  appearActiveClass: String,\n  appearToClass: String,\n  leaveFromClass: String,\n  leaveActiveClass: String,\n  leaveToClass: String,\n};\n\nconst Transition = (props: TransitionProps, { slots }: { slots: any }): VNode | null => {\n  const innerProps = resolveTransitionProps(props);\n  const children = slots.default && slots.default();\n\n  if (!children || children.length === 0) {\n    return null;\n  }\n\n  const child = children[0];\n  if (child) {\n    child.transition = innerProps;\n  }\n\n  return child;\n};\n\n(Transition as any).displayName = \"Transition\";\n(Transition as any).props = TransitionPropsValidators;\n\nexport { Transition };\n"
  },
  {
    "path": "impl/runtime-dom/src/directives/vModel.ts",
    "content": "import type {\n  DirectiveBinding,\n  DirectiveHook,\n  ObjectDirective,\n  VNode,\n} from \"@chibivue/runtime-core\";\nimport { addEventListener } from \"../modules/events\";\n\ntype AssignerFn = (value: any) => void;\nconst getModelAssigner = (vnode: VNode): AssignerFn => {\n  return vnode.props![\"onUpdate:modelValue\"];\n};\n\ntype ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>;\n\nexport const vModelText: ModelDirective<HTMLInputElement | HTMLTextAreaElement> = {\n  created(el, _, vnode) {\n    el._assign = getModelAssigner(vnode);\n    addEventListener(el, \"input\", (e) => {\n      el._assign(el.value);\n    });\n  },\n  mounted(el, { value }) {\n    el.value = value == null ? \"\" : value;\n  },\n  beforeUpdate(el, { value }, vnode) {\n    el._assign = getModelAssigner(vnode);\n    const newValue = value == null ? \"\" : value;\n    if (el.value !== newValue) {\n      el.value = newValue;\n    }\n  },\n};\n\nexport const vModelDynamic: ObjectDirective<HTMLInputElement | HTMLTextAreaElement> = {\n  created(el, binding, vnode) {\n    callModelHook(el, binding, vnode, null, \"created\");\n  },\n  mounted(el, binding, vnode) {\n    callModelHook(el, binding, vnode, null, \"mounted\");\n  },\n  beforeUpdate(el, binding, vnode, prevVNode) {\n    callModelHook(el, binding, vnode, prevVNode, \"beforeUpdate\");\n  },\n  updated(el, binding, vnode, prevVNode) {\n    callModelHook(el, binding, vnode, prevVNode, \"updated\");\n  },\n};\n\n// TODO: impl checkbox, radio, select\nfunction resolveDynamicModel(tagName: string, type: string | undefined) {\n  switch (tagName) {\n    default:\n      switch (type) {\n        default:\n          return vModelText;\n      }\n  }\n}\n\nfunction callModelHook(\n  el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,\n  binding: DirectiveBinding,\n  vnode: VNode,\n  prevVNode: VNode | null,\n  hook: keyof ObjectDirective,\n) {\n  const modelToUse = resolveDynamicModel(el.tagName, vnode.props && vnode.props.type);\n  const fn = modelToUse[hook] as DirectiveHook;\n  fn && fn(el, binding, vnode, prevVNode);\n}\n"
  },
  {
    "path": "impl/runtime-dom/src/directives/vOn.ts",
    "content": "import { hyphenate } from \"@chibivue/shared\";\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\n\ntype KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent;\n\nconst modifierGuards: Record<string, (e: Event, modifiers: string[]) => void | boolean> = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !(e as KeyedEvent).ctrlKey,\n  shift: (e) => !(e as KeyedEvent).shiftKey,\n  alt: (e) => !(e as KeyedEvent).altKey,\n  meta: (e) => !(e as KeyedEvent).metaKey,\n  left: (e) => \"button\" in e && (e as MouseEvent).button !== 0,\n  middle: (e) => \"button\" in e && (e as MouseEvent).button !== 1,\n  right: (e) => \"button\" in e && (e as MouseEvent).button !== 2,\n  exact: (e, modifiers) =>\n    systemModifiers.some((m) => (e as any)[`${m}Key`] && !modifiers.includes(m)),\n};\n\nexport const withModifiers = (\n  fn: Function,\n  modifiers: string[],\n): ((event: Event, ...args: unknown[]) => unknown) => {\n  return (event: Event, ...args: unknown[]): unknown => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  };\n};\n\nconst keyNames: Record<string, string | string[]> = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\",\n};\n\nexport const withKeys = (\n  fn: Function,\n  modifiers: string[],\n): ((event: KeyboardEvent) => unknown) => {\n  return (event: KeyboardEvent): unknown => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  };\n};\n"
  },
  {
    "path": "impl/runtime-dom/src/directives/vShow.ts",
    "content": "import type { ObjectDirective } from \"@chibivue/runtime-core\";\n\nexport const vShow: ObjectDirective<HTMLElement> = {\n  beforeMount(el, { value }, { transition }) {\n    el._vod = el.style.display === \"none\" ? \"\" : el.style.display;\n    if (transition && value) {\n      transition.beforeEnter(el);\n    } else {\n      setDisplay(el, value);\n    }\n  },\n  mounted(el, { value }, { transition }) {\n    if (transition && value) {\n      transition.enter(el);\n    }\n  },\n  updated(el, { value, oldValue }, { transition }) {\n    if (!value === !oldValue) return;\n    if (transition) {\n      if (value) {\n        transition.beforeEnter(el);\n        setDisplay(el, true);\n        transition.enter(el);\n      } else {\n        transition.leave(el, () => {\n          setDisplay(el, false);\n        });\n      }\n    } else {\n      setDisplay(el, value);\n    }\n  },\n  beforeUnmount(el, { value }) {\n    setDisplay(el, value);\n  },\n};\n\nfunction setDisplay(el: HTMLElement, value: unknown): void {\n  el.style.display = value ? el._vod : \"none\";\n}\n\ndeclare global {\n  interface HTMLElement {\n    _vod: string;\n  }\n}\n"
  },
  {
    "path": "impl/runtime-dom/src/index.ts",
    "content": "import type { CreateAppFunction, RootRenderFunction } from \"@chibivue/runtime-core\";\nimport { createRenderer } from \"@chibivue/runtime-core\";\nimport { isString } from \"@chibivue/shared\";\n\nimport { nodeOps } from \"./nodeOps\";\nimport { patchProp } from \"./patchProp\";\n\nconst renderer = createRenderer({ ...nodeOps, patchProp });\n\nexport const render = ((...args) => {\n  renderer.render(...args);\n}) as RootRenderFunction<Element>;\n\nexport const createApp = ((...args) => {\n  const app = renderer.createApp(...args);\n  const { mount } = app;\n  app.mount = (containerOrSelector: Element | string): any => {\n    const container = normalizeContainer(containerOrSelector);\n    if (!container) return;\n    mount(container);\n  };\n  return app;\n}) as CreateAppFunction<Element>;\n\nfunction normalizeContainer(container: Element | string): Element | null {\n  if (isString(container)) {\n    const res = document.querySelector(container);\n    return res;\n  } else {\n    return container;\n  }\n}\n\nexport { vModelText, vModelDynamic } from \"./directives/vModel\";\n\n// re-export everything from core\n// h, Component, reactivity API, nextTick, flags & types\nexport * from \"@chibivue/runtime-core\";\n\nexport * from \"./directives/vOn\";\nexport * from \"./directives/vModel\";\nexport * from \"./directives/vShow\";\nexport * from \"./runtimeHelpers\";\nexport * from \"./nodeOps\";\nexport * from \"./patchProp\";\nexport * from \"./components/Transition\";\n"
  },
  {
    "path": "impl/runtime-dom/src/modules/attrs.ts",
    "content": "export function patchAttr(el: Element, key: string, value: any): void {\n  if (value == null) {\n    el.removeAttribute(key);\n  } else {\n    el.setAttribute(key, value);\n  }\n}\n"
  },
  {
    "path": "impl/runtime-dom/src/modules/events.ts",
    "content": "interface Invoker extends EventListener {\n  value: EventValue;\n}\n\ntype EventValue = Function;\n\nexport function addEventListener(el: Element, event: string, handler: EventListener): void {\n  el.addEventListener(event, handler);\n}\n\nexport function removeEventListener(el: Element, event: string, handler: EventListener): void {\n  el.removeEventListener(event, handler);\n}\n\nexport function patchEvent(\n  el: Element & { _vei?: Record<string, Invoker | undefined> },\n  rawName: string,\n  nextValue: EventValue | null,\n): void {\n  // vei = vue event invokers\n  const invokers = el._vei || (el._vei = {});\n  const existingInvoker = invokers[rawName];\n  if (nextValue && existingInvoker) {\n    // patch\n    existingInvoker.value = nextValue;\n  } else {\n    const name = parseName(rawName);\n    if (nextValue) {\n      // add\n      const invoker = (invokers[rawName] = createInvoker(nextValue));\n      addEventListener(el, name, invoker);\n    } else if (existingInvoker) {\n      // remove\n      removeEventListener(el, name, existingInvoker);\n      invokers[rawName] = undefined;\n    }\n  }\n}\n\nfunction parseName(rawName: string): string {\n  return rawName.slice(2).toLocaleLowerCase();\n}\n\nfunction createInvoker(initialValue: EventValue) {\n  const invoker: Invoker = (e: Event & { _vts?: number }) => {\n    invoker.value(e);\n  };\n  invoker.value = initialValue;\n  return invoker;\n}\n"
  },
  {
    "path": "impl/runtime-dom/src/modules/style.ts",
    "content": "import { isArray, isString } from \"@chibivue/shared\";\n\ntype Style = string | Record<string, string | string[]> | null;\n\nexport function patchStyle(el: Element, prev: Style, next: Style): void {\n  const style = (el as HTMLElement).style;\n  const isCssString = isString(next);\n  if (next && !isCssString) {\n    if (prev && !isString(prev)) {\n      for (const key in prev) {\n        if (next[key] == null) {\n          setStyle(style, key, \"\");\n        }\n      }\n    }\n    for (const key in next) {\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        style.cssText = next as string;\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n}\n\nfunction setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    if (name.startsWith(\"--\")) {\n      // custom property definition\n      style.setProperty(name, val);\n    } else {\n      style[name as any] = val;\n    }\n  }\n}\n"
  },
  {
    "path": "impl/runtime-dom/src/nodeOps.ts",
    "content": "import type { RendererOptions } from \"@chibivue/runtime-core\";\n\nexport const nodeOps: Omit<RendererOptions, \"patchProp\"> = {\n  createElement: (tagName) => {\n    return document.createElement(tagName);\n  },\n\n  createText: (text: string) => {\n    return document.createTextNode(text);\n  },\n\n  createComment: (text: string) => {\n    return document.createComment(text);\n  },\n\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n\n  setElementText: (el, text) => {\n    el.textContent = text;\n  },\n\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n\n  parentNode: (node) => {\n    return node.parentNode;\n  },\n\n  nextSibling: (node) => {\n    return node.nextSibling;\n  },\n\n  querySelector: (selector) => {\n    return document.querySelector(selector);\n  },\n};\n"
  },
  {
    "path": "impl/runtime-dom/src/patchProp.ts",
    "content": "import { isOn } from \"@chibivue/shared\";\nimport { patchEvent } from \"./modules/events\";\nimport type { RendererOptions } from \"@chibivue/runtime-core\";\nimport { patchAttr } from \"./modules/attrs\";\nimport { patchStyle } from \"./modules/style\";\n\ntype DOMRendererOptions = RendererOptions<Node, Element>;\n\nexport const patchProp: DOMRendererOptions[\"patchProp\"] = (el, key, prevValue, nextValue) => {\n  // if (key === \"class\") {\n  //   // TODO: patch class\n  // }\n  if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    patchEvent(el, key, nextValue);\n  } else {\n    patchAttr(el, key, nextValue);\n  }\n};\n"
  },
  {
    "path": "impl/runtime-dom/src/runtimeHelpers.ts",
    "content": "// Re-export from compiler-dom for backward compatibility\n// The actual symbols are now defined in compiler-dom to avoid circular dependencies\nexport { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from \"@chibivue/compiler-dom\";\n"
  },
  {
    "path": "impl/runtime-vapor/README.md",
    "content": "# WARNING\n\nVapor Mode is a new feature that will be implemented in Vue.js in the future; its implementation is not currently publicly available.\n\nThis directory is completely implemented by the author (@ubugeeei) for fun and is completely different from the original code.\n"
  },
  {
    "path": "impl/runtime-vapor/package.json",
    "content": "{\n  \"name\": \"@chibivue/runtime-vapor\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/runtime-vapor/src/apiCreateVaporApp.ts",
    "content": "import type { App, AppContext, AppConfig, VNode, Component, Plugin } from \"@chibivue/runtime-core\";\nimport { createAppContext, createVNode } from \"@chibivue/runtime-core\";\nimport type { VaporComponentInternalInstance, VaporComponent } from \"./component\";\nimport { createVaporComponentInstance, initialRenderVaporComponent } from \"./component\";\nimport { hydrateVaporComponent } from \"./hydration\";\nimport { setCurrentVaporInstance } from \".\";\n\nexport interface VaporApp extends App {\n  _component: VaporComponent;\n  _context: AppContext;\n  _container: Element | null;\n  _instance: VaporComponentInternalInstance | null;\n}\n\n/**\n * Create a Vapor application for client-side rendering.\n * This is the standard way to create a Vapor app.\n */\nexport function createVaporApp(rootComponent: VaporComponent): VaporApp {\n  const context = createAppContext();\n  const installedPlugins = new Set<Plugin>();\n\n  const config: AppConfig = {\n    globalProperties: {},\n  };\n\n  const app: VaporApp = {\n    _component: rootComponent,\n    _props: null,\n    _context: context,\n    _container: null,\n    _instance: null,\n    config,\n\n    use(plugin: Plugin, ...options: any[]) {\n      if (installedPlugins.has(plugin)) return app;\n      installedPlugins.add(plugin);\n      plugin.install(app, ...options);\n      return app;\n    },\n\n    component(name: string, component: Component) {\n      context.components[name] = component;\n      return app;\n    },\n\n    provide(key: any, value: any) {\n      context.provides[key] = value;\n      return app;\n    },\n\n    mount(containerOrSelector: Element | string) {\n      const container =\n        typeof containerOrSelector === \"string\"\n          ? document.querySelector(containerOrSelector)\n          : containerOrSelector;\n\n      if (!container) {\n        console.warn(\"Failed to mount app: container not found\");\n        return;\n      }\n\n      app._container = container;\n\n      // Create VNode for the root component\n      const vnode = createVNode(rootComponent as any);\n      vnode.appContext = context;\n\n      // Create and render the component\n      const instance = createVaporComponentInstance(vnode, null);\n      app._instance = instance;\n\n      setCurrentVaporInstance(instance);\n      const el = initialRenderVaporComponent(instance);\n      setCurrentVaporInstance(null);\n\n      // Clear container and append rendered element\n      container.innerHTML = \"\";\n      container.appendChild(el);\n\n      // Mark as mounted\n      instance.isMounted = true;\n    },\n  } as VaporApp;\n\n  context.app = app;\n\n  return app;\n}\n\n/**\n * Create a Vapor SSR application for hydration.\n *\n * This function is used on the client-side to hydrate SSR-rendered Vapor components.\n * The key differences from createVaporApp:\n * 1. mount() hydrates existing DOM instead of replacing it\n * 2. Template calls find existing elements instead of creating new ones\n * 3. Event listeners are attached to existing elements\n *\n * Usage:\n * ```ts\n * // Server-side\n * const html = await renderToString(createVNode(App));\n *\n * // Client-side\n * createVaporSSRApp(App).mount('#app');\n * ```\n */\nexport function createVaporSSRApp(rootComponent: VaporComponent): VaporApp {\n  const context = createAppContext();\n  const installedPlugins = new Set<Plugin>();\n\n  const config: AppConfig = {\n    globalProperties: {},\n  };\n\n  const app: VaporApp = {\n    _component: rootComponent,\n    _props: null,\n    _context: context,\n    _container: null,\n    _instance: null,\n    config,\n\n    use(plugin: Plugin, ...options: any[]) {\n      if (installedPlugins.has(plugin)) return app;\n      installedPlugins.add(plugin);\n      plugin.install(app, ...options);\n      return app;\n    },\n\n    component(name: string, component: Component) {\n      context.components[name] = component;\n      return app;\n    },\n\n    provide(key: any, value: any) {\n      context.provides[key] = value;\n      return app;\n    },\n\n    mount(containerOrSelector: Element | string) {\n      const container =\n        typeof containerOrSelector === \"string\"\n          ? document.querySelector(containerOrSelector)\n          : containerOrSelector;\n\n      if (!container) {\n        console.warn(\"Failed to mount app: container not found\");\n        return;\n      }\n\n      app._container = container;\n\n      // Check if there's SSR content to hydrate\n      if (container.hasChildNodes()) {\n        // Hydration mode\n        const vnode = createVNode(rootComponent as any);\n        vnode.appContext = context;\n\n        const instance = hydrateVaporComponent(vnode, container, null);\n        app._instance = instance;\n      } else {\n        // No SSR content, fall back to normal mount\n        const vnode = createVNode(rootComponent as any);\n        vnode.appContext = context;\n\n        const instance = createVaporComponentInstance(vnode, null);\n        app._instance = instance;\n\n        setCurrentVaporInstance(instance);\n        const el = initialRenderVaporComponent(instance);\n        setCurrentVaporInstance(null);\n\n        container.appendChild(el);\n        instance.isMounted = true;\n      }\n    },\n  } as VaporApp;\n\n  context.app = app;\n\n  return app;\n}\n"
  },
  {
    "path": "impl/runtime-vapor/src/component.ts",
    "content": "import type { AppContext, VNode } from \"@chibivue/runtime-core\";\nimport { createAppContext } from \"@chibivue/runtime-core\";\n\nimport type { ComponentInternalInstance, Data, LifecycleHook } from \"@chibivue/runtime-core\";\nimport { LifecycleHooks, setCurrentInstance, unsetCurrentInstance } from \"@chibivue/runtime-core\";\n\nimport type { VaporNode } from \".\";\n\nexport type VaporComponent = (self: VaporComponentInternalInstance) => VaporNode;\n\nexport interface VaporComponentInternalInstance {\n  __is_vapor: true;\n  uid: number;\n  type: VaporComponent;\n  parent: ComponentInternalInstance | VaporComponentInternalInstance | null;\n  appContext: AppContext;\n\n  provides: Data;\n\n  isMounted: boolean;\n  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook;\n  [LifecycleHooks.MOUNTED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook;\n  [LifecycleHooks.UPDATED]: LifecycleHook;\n  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;\n  [LifecycleHooks.UNMOUNTED]: LifecycleHook;\n}\n\nlet uid = 0;\nexport const createVaporComponentInstance = (\n  vnode: VNode,\n  parent?: ComponentInternalInstance | VaporComponentInternalInstance | null,\n): VaporComponentInternalInstance => {\n  const appContext = (parent ? parent.appContext : vnode.appContext) || createAppContext();\n\n  const instance: VaporComponentInternalInstance = {\n    __is_vapor: true,\n    uid: uid++,\n    type: vnode.type as VaporComponent,\n    parent: parent ?? null,\n    appContext,\n\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n\n    isMounted: false,\n    [LifecycleHooks.BEFORE_MOUNT]: null,\n    [LifecycleHooks.MOUNTED]: null,\n    [LifecycleHooks.BEFORE_UPDATE]: null,\n    [LifecycleHooks.UPDATED]: null,\n    [LifecycleHooks.BEFORE_UNMOUNT]: null,\n    [LifecycleHooks.UNMOUNTED]: null,\n  };\n  return instance;\n};\n\nexport const initialRenderVaporComponent = (\n  instance: VaporComponentInternalInstance,\n): VaporNode => {\n  setCurrentInstance(instance as any); //TODO: types\n  const el = instance.type(instance);\n  unsetCurrentInstance();\n  return el;\n};\n\nexport const isVapor = (\n  instance: ComponentInternalInstance | VaporComponentInternalInstance,\n): instance is VaporComponentInternalInstance => {\n  return (instance as any).__is_vapor;\n};\n"
  },
  {
    "path": "impl/runtime-vapor/src/hydration.ts",
    "content": "import type { VaporComponentInternalInstance, VaporComponent } from \"./component\";\nimport { createVaporComponentInstance } from \"./component\";\nimport type { VNode, AppContext } from \"@chibivue/runtime-core\";\nimport { setCurrentInstance, unsetCurrentInstance } from \"@chibivue/runtime-core\";\nimport { effect } from \"@chibivue/reactivity\";\nimport { invokeArrayFns } from \"@chibivue/shared\";\n\nexport interface VaporHydrationContext {\n  // Current DOM node being hydrated\n  node: Node | null;\n  // Parent element\n  parent: Element;\n}\n\n/**\n * Hydrate a Vapor component against existing SSR-rendered DOM.\n *\n * In Vapor SSR:\n * - Server uses standard VNode SSR (compiler-ssr) to generate HTML\n * - Client uses createVaporSSRApp to hydrate and attach reactivity\n *\n * The hydration process:\n * 1. Find existing DOM elements (rendered by SSR)\n * 2. Execute Vapor component setup logic\n * 3. Attach event listeners\n * 4. Set up reactive effects\n */\nexport function hydrateVaporComponent(\n  vnode: VNode,\n  container: Element,\n  parentInstance: VaporComponentInternalInstance | null = null,\n): VaporComponentInternalInstance {\n  const instance = createVaporComponentInstance(vnode, parentInstance);\n\n  // Set up hydration context\n  const ctx: VaporHydrationContext = {\n    node: container.firstChild,\n    parent: container,\n  };\n\n  // Execute component with hydration mode\n  setCurrentInstance(instance as any);\n\n  // Store hydration context for template() calls\n  (instance as any).__hydrationCtx = ctx;\n\n  try {\n    const comp = instance.type as VaporComponent;\n    // Run the component - in hydration mode, template() will find existing DOM\n    const el = comp(instance);\n\n    // Mark as mounted\n    instance.isMounted = true;\n\n    // Invoke mounted hooks\n    const { m } = instance as any;\n    if (m) {\n      invokeArrayFns(m);\n    }\n\n    return instance;\n  } finally {\n    unsetCurrentInstance();\n    delete (instance as any).__hydrationCtx;\n  }\n}\n\n/**\n * Hydrate template - find existing DOM element instead of creating new one.\n * This is used during client-side hydration of SSR-rendered Vapor components.\n */\nexport function hydrateTemplate(ctx: VaporHydrationContext, _html: string): Element {\n  // In hydration mode, we don't create new elements\n  // Instead, we return the existing SSR-rendered element\n  const el = ctx.node as Element;\n\n  // Move to next sibling for subsequent template() calls\n  if (el) {\n    ctx.node = el.nextSibling;\n  }\n\n  return el;\n}\n\n/**\n * Hydrate render effect - set up reactive effect using existing DOM.\n * The effect will update the already-hydrated DOM when reactive values change.\n */\nexport function hydrateRenderEffect(\n  instance: VaporComponentInternalInstance,\n  fn: () => void,\n): void {\n  effect(() => {\n    // Before update: call onBeforeUpdate hooks (only after mount)\n    if (instance.isMounted) {\n      const { bu } = instance as any;\n      if (bu) {\n        invokeArrayFns(bu);\n      }\n    }\n\n    // Execute the update\n    fn();\n\n    // After update: call onUpdated hooks (only after mount)\n    if (instance.isMounted) {\n      const { u } = instance as any;\n      if (u) {\n        queueMicrotask(() => {\n          invokeArrayFns(u);\n        });\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "impl/runtime-vapor/src/index.ts",
    "content": "import type { RootRenderFunction, VNode } from \"@chibivue/runtime-core\";\nimport { createRenderer, LifecycleHooks } from \"@chibivue/runtime-core\";\nimport { nodeOps, patchProp } from \"@chibivue/runtime-dom\";\nimport { effect, getCurrentScope, EffectScope } from \"@chibivue/reactivity\";\nimport { invokeArrayFns } from \"@chibivue/shared\";\n\nimport type { VaporComponentInternalInstance } from \"./component\";\n\nexport * from \"./component\";\nexport * from \"./hydration\";\nexport * from \"./apiCreateVaporApp\";\n\nexport type VaporNode = Element & { __is_vapor: true };\n\n// Current Vapor component instance being rendered\nlet currentInstance: VaporComponentInternalInstance | null = null;\n\nexport const setCurrentVaporInstance = (instance: VaporComponentInternalInstance | null): void => {\n  currentInstance = instance;\n};\n\nexport const getCurrentVaporInstance = (): VaporComponentInternalInstance | null => {\n  return currentInstance;\n};\n\nexport const template = (tmp: string): VaporNode => {\n  const container = document.createElement(\"div\");\n  container.innerHTML = tmp;\n  const el = container.firstElementChild as VaporNode;\n  el.__is_vapor = true;\n  return el;\n};\n\n/**\n * renderEffect - Core mechanism for reactive DOM updates in Vapor mode\n *\n * Unlike Virtual DOM's diff-based approach, renderEffect directly tracks\n * reactive dependencies and updates the DOM when they change.\n *\n * How it works:\n * 1. Wraps a DOM update function in a reactive effect\n * 2. Automatically tracks which reactive values are accessed\n * 3. Re-runs the update function when tracked values change\n * 4. Updates only the specific DOM nodes that need changes\n *\n * Important: renderEffect also handles lifecycle hooks:\n * - Calls onBeforeUpdate hooks before each update (after initial mount)\n * - Calls onUpdated hooks after each update (after initial mount)\n *\n * Example generated code:\n *   renderEffect(() => {\n *     setText(el, \"\", count.value)\n *   })\n *\n * When count.value changes:\n * 1. onBeforeUpdate hooks are called\n * 2. The text content is updated\n * 3. onUpdated hooks are called (in a microtask)\n */\nexport const renderEffect = (fn: () => void): void => {\n  const instance = currentInstance;\n\n  effect(() => {\n    // Before update: call onBeforeUpdate hooks (only after mount)\n    if (instance?.isMounted) {\n      const { bu } = instance as any;\n      if (bu) {\n        invokeArrayFns(bu);\n      }\n    }\n\n    // Execute the update\n    fn();\n\n    // After update: call onUpdated hooks (only after mount)\n    if (instance?.isMounted) {\n      const { u } = instance as any;\n      if (u) {\n        // Queue updated hooks to run after the current microtask\n        queueMicrotask(() => {\n          invokeArrayFns(u);\n        });\n      }\n    }\n  });\n};\n\nexport const setText = (target: Element, format: string, ...values: any[]): void => {\n  const fmt = (): string => {\n    let text = format;\n    for (let i = 0; i < values.length; i++) {\n      text = text.replace(\"{}\", values[i]);\n    }\n    return text;\n  };\n\n  if (!target) return;\n\n  if (!values.length) {\n    target.textContent = fmt();\n    return;\n  }\n\n  if (!format && values.length) {\n    target.textContent = values.join(\"\");\n    return;\n  }\n\n  target.textContent = fmt();\n};\n\nexport const on = (element: Element, event: string, callback: () => void): void => {\n  element.addEventListener(event, callback);\n};\n\nexport const setClass = (element: Element, value: string | object | any[]): void => {\n  if (typeof value === \"string\") {\n    element.className = value;\n  } else if (Array.isArray(value)) {\n    element.className = value.filter(Boolean).join(\" \");\n  } else if (typeof value === \"object\" && value !== null) {\n    const classes: string[] = [];\n    for (const [key, val] of Object.entries(value)) {\n      if (val) classes.push(key);\n    }\n    element.className = classes.join(\" \");\n  }\n};\n\nexport const setStyle = (\n  element: Element,\n  value: string | Record<string, string | number>,\n): void => {\n  const el = element as HTMLElement;\n  if (typeof value === \"string\") {\n    el.style.cssText = value;\n  } else if (typeof value === \"object\" && value !== null) {\n    for (const [key, val] of Object.entries(value)) {\n      el.style.setProperty(\n        key.startsWith(\"--\") ? key : key.replace(/([A-Z])/g, \"-$1\").toLowerCase(),\n        typeof val === \"number\" ? `${val}px` : val,\n      );\n    }\n  }\n};\n\nexport const setAttr = (element: Element, key: string, value: any): void => {\n  if (value == null || value === false) {\n    element.removeAttribute(key);\n  } else {\n    element.setAttribute(key, value === true ? \"\" : String(value));\n  }\n};\n\n/*\n *\n * for non vapor component\n *\n */\n\nconst renderer = createRenderer({ ...nodeOps, patchProp });\n\nconst render = ((...args) => renderer.render(...args)) as RootRenderFunction<Element>;\n\nexport const createComponent = (\n  self: VaporComponentInternalInstance,\n  component: VNode,\n  container: VaporNode,\n): void => render(component, container, self);\n"
  },
  {
    "path": "impl/server-renderer/package.json",
    "content": "{\n  \"name\": \"@chibivue/server-renderer\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\",\n  \"peerDependencies\": {\n    \"@chibivue/runtime-core\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "impl/server-renderer/src/helpers/ssrInterpolate.ts",
    "content": "import { toDisplayString } from \"@chibivue/shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrInterpolate(value: unknown): string {\n  return escapeHtml(toDisplayString(value));\n}\n"
  },
  {
    "path": "impl/server-renderer/src/helpers/ssrRenderAttrs.ts",
    "content": "import {\n  isArray,\n  isFunction,\n  isOn,\n  isString,\n  normalizeClass,\n  normalizeStyle,\n} from \"@chibivue/shared\";\nimport { escapeHtml } from \"./ssrUtils\";\n\nexport function ssrRenderAttrs(props: Record<string, unknown>, tag?: string): string {\n  let ret = \"\";\n  for (const key in props) {\n    if (ssrIsIgnoredKey(key) || isOn(key) || (tag === \"textarea\" && key === \"value\")) {\n      continue;\n    }\n    const value = props[key];\n    if (key === \"class\") {\n      ret += ` class=\"${ssrRenderClass(value)}\"`;\n    } else if (key === \"style\") {\n      ret += ` style=\"${ssrRenderStyle(value)}\"`;\n    } else {\n      ret += ssrRenderDynamicAttr(key, value, tag);\n    }\n  }\n  return ret;\n}\n\nfunction ssrIsIgnoredKey(key: string): boolean {\n  return key === \"key\" || key === \"ref\" || key === \"innerHTML\" || key === \"textContent\";\n}\n\nexport function ssrRenderDynamicAttr(key: string, value: unknown, tag?: string): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  const attrKey =\n    tag && (tag.indexOf(\"-\") > 0 || isSVGTag(tag)) ? key : propsToAttrMap[key] || key.toLowerCase();\n\n  if (isBooleanAttr(attrKey)) {\n    return value === false ? \"\" : ` ${attrKey}`;\n  } else if (isSSRSafeAttrName(attrKey)) {\n    return value === \"\" ? ` ${attrKey}` : ` ${attrKey}=\"${escapeHtml(value)}\"`;\n  } else {\n    console.warn(`[@chibivue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`);\n    return \"\";\n  }\n}\n\nexport function ssrRenderAttr(key: string, value: unknown): string {\n  if (!isRenderableAttrValue(value)) {\n    return \"\";\n  }\n  return ` ${key}=\"${escapeHtml(value)}\"`;\n}\n\nfunction isRenderableAttrValue(value: unknown): boolean {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nexport function ssrRenderClass(raw: unknown): string {\n  return escapeHtml(normalizeClass(raw));\n}\n\nexport function ssrRenderStyle(raw: unknown): string {\n  if (!raw) {\n    return \"\";\n  }\n  if (isString(raw)) {\n    return escapeHtml(raw);\n  }\n  const styles = normalizeStyle(raw);\n  return escapeHtml(stringifyStyle(styles as Record<string, string | number> | null));\n}\n\nfunction stringifyStyle(styles: Record<string, string | number> | null): string {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    const normalizedKey = key.startsWith(\"--\") ? key : hyphenate(key);\n    if (isString(value) || typeof value === \"number\") {\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\n\nfunction hyphenate(str: string): string {\n  return str.replace(/\\B([A-Z])/g, \"-$1\").toLowerCase();\n}\n\n// Maps props to their corresponding HTML attribute names\nconst propsToAttrMap: Record<string, string> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n\n// Boolean attributes\nconst isBooleanAttr = (key: string): boolean => booleanAttrsSet.has(key);\n\nconst booleanAttrsSet = new Set(\n  (\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n    \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n    \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n    \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n    \"required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"\n  ).split(\",\"),\n);\n\n// Checks if the attribute name is safe for SSR\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nfunction isSSRSafeAttrName(name: string): boolean {\n  return !unsafeAttrCharRE.test(name);\n}\n\n// SVG tags\nconst SVG_TAGS = new Set(\n  \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\".split(\n    \",\",\n  ),\n);\n\nfunction isSVGTag(tag: string): boolean {\n  return SVG_TAGS.has(tag);\n}\n"
  },
  {
    "path": "impl/server-renderer/src/helpers/ssrRenderList.ts",
    "content": "import { isArray, isObject, isString } from \"@chibivue/shared\";\n\nexport function ssrRenderList(\n  source: unknown,\n  renderItem: (value: unknown, key: string | number, index?: number) => void,\n): void {\n  if (isArray(source) || isString(source)) {\n    for (let i = 0, l = source.length; i < l; i++) {\n      renderItem(source[i], i);\n    }\n  } else if (typeof source === \"number\") {\n    for (let i = 0; i < source; i++) {\n      renderItem(i + 1, i);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator as any]) {\n      const arr = Array.from(source as Iterable<any>);\n      for (let i = 0, l = arr.length; i < l; i++) {\n        renderItem(arr[i], i);\n      }\n    } else {\n      const keys = Object.keys(source);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        renderItem((source as Record<string, unknown>)[key], key, i);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "impl/server-renderer/src/helpers/ssrUtils.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n    lastIndex = index + 1;\n    html += escaped;\n  }\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n\n// void elements\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nconst isVoidTagSet = new Set(VOID_TAGS.split(\",\"));\nexport function isVoidTag(tag: string): boolean {\n  return isVoidTagSet.has(tag);\n}\n"
  },
  {
    "path": "impl/server-renderer/src/index.ts",
    "content": "// public\nexport type { SSRContext } from \"./render\";\nexport { renderToString } from \"./renderToString\";\n\n// vapor SSR\nexport { renderVaporComponentToString, ssrVaporTemplate, ssrVaporSetText } from \"./renderVapor\";\n\n// internal runtime helpers\nexport { ssrInterpolate } from \"./helpers/ssrInterpolate\";\nexport { ssrRenderList } from \"./helpers/ssrRenderList\";\nexport {\n  ssrRenderAttrs,\n  ssrRenderClass,\n  ssrRenderStyle,\n  ssrRenderAttr,\n  ssrRenderDynamicAttr,\n} from \"./helpers/ssrRenderAttrs\";\n"
  },
  {
    "path": "impl/server-renderer/src/render.ts",
    "content": "import {\n  Comment,\n  type Component,\n  type ComponentInternalInstance,\n  type DirectiveBinding,\n  Fragment,\n  Text,\n  type VNode,\n  type VNodeArrayChildren,\n  type VNodeProps,\n  mergeProps,\n  createComponentInstance,\n  setupComponent,\n  setCurrentInstance,\n  unsetCurrentInstance,\n  normalizeVNode,\n} from \"@chibivue/runtime-core\";\nimport { ShapeFlags, isArray, isFunction, isPromise, isString } from \"@chibivue/shared\";\nimport { ssrRenderAttrs } from \"./helpers/ssrRenderAttrs\";\nimport { escapeHtml, escapeHtmlComment, isVoidTag } from \"./helpers/ssrUtils\";\n\nexport type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean };\nexport type SSRBufferItem = string | SSRBuffer | Promise<SSRBuffer>;\nexport type PushFn = (item: SSRBufferItem) => void;\nexport type Props = Record<string, unknown>;\n\nexport type SSRContext = {\n  [key: string]: any;\n  teleports?: Record<string, string>;\n  /**\n   * @internal\n   */\n  __teleportBuffers?: Record<string, SSRBuffer>;\n  /**\n   * @internal\n   */\n  __watcherHandles?: (() => void)[];\n};\n\n// Each component has a buffer array.\nexport function createBuffer(): { getBuffer: () => SSRBuffer; push: PushFn } {\n  let appendable = false;\n  const buffer: SSRBuffer = [];\n  return {\n    getBuffer(): SSRBuffer {\n      return buffer;\n    },\n    push(item: SSRBufferItem): void {\n      const isStringItem = isString(item);\n      if (appendable && isStringItem) {\n        buffer[buffer.length - 1] += item as string;\n        return;\n      }\n      buffer.push(item);\n      appendable = isStringItem;\n      if (isPromise(item) || (isArray(item) && item.hasAsync)) {\n        buffer.hasAsync = true;\n      }\n    },\n  };\n}\n\nexport function renderComponentVNode(\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = (vnode.component = createComponentInstance(vnode, parentComponent));\n  const res = setupComponent(instance);\n  const hasAsyncSetup = isPromise(res);\n\n  if (hasAsyncSetup) {\n    return (res as Promise<void>).then(() => renderComponentSubTree(instance));\n  } else {\n    return renderComponentSubTree(instance);\n  }\n}\n\nfunction renderComponentSubTree(\n  instance: ComponentInternalInstance,\n): SSRBuffer | Promise<SSRBuffer> {\n  const comp = instance.type as Component;\n  const { getBuffer, push } = createBuffer();\n\n  if (isFunction(comp)) {\n    const root = comp(instance.props, {\n      slots: instance.slots,\n      emit: instance.emit,\n      attrs: {},\n    });\n    if (root) {\n      renderVNode(push, normalizeVNode(root), instance);\n    }\n  } else if (instance.render) {\n    const prev = setCurrentInstance(instance);\n    try {\n      const root = instance.render(instance.proxy!, instance.data, instance.ctx);\n      if (root) {\n        instance.subTree = normalizeVNode(root);\n        renderVNode(push, instance.subTree, instance);\n      }\n    } finally {\n      unsetCurrentInstance();\n    }\n  } else {\n    console.warn(`Component is missing render function.`);\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\nexport function renderVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const { type, shapeFlag, children, dirs, props } = vnode;\n\n  if (dirs) {\n    vnode.props = applySSRDirectives(vnode, props, dirs);\n  }\n\n  switch (type) {\n    case Text:\n      push(escapeHtml(children as string));\n      break;\n    case Comment:\n      push(children ? `<!--${escapeHtmlComment(children as string)}-->` : `<!---->`);\n      break;\n    case Fragment:\n      push(`<!--[-->`);\n      renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      push(`<!--]-->`);\n      break;\n    default:\n      if (shapeFlag & ShapeFlags.ELEMENT) {\n        renderElementVNode(push, vnode, parentComponent);\n      } else if (shapeFlag & ShapeFlags.COMPONENT) {\n        push(renderComponentVNode(vnode, parentComponent));\n      } else if (shapeFlag & ShapeFlags.TELEPORT) {\n        renderTeleportVNode(push, vnode, parentComponent);\n      }\n  }\n}\n\nexport function renderVNodeChildren(\n  push: PushFn,\n  children: VNodeArrayChildren,\n  parentComponent: ComponentInternalInstance,\n): void {\n  for (let i = 0; i < children.length; i++) {\n    renderVNode(push, normalizeVNode(children[i]), parentComponent);\n  }\n}\n\nfunction renderElementVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const tag = vnode.type as string;\n  const { props, children, shapeFlag } = vnode;\n  let openTag = `<${tag}`;\n\n  if (props) {\n    openTag += ssrRenderAttrs(props, tag);\n  }\n\n  push(openTag + `>`);\n\n  if (!isVoidTag(tag)) {\n    let hasChildrenOverride = false;\n    if (props) {\n      if (props.innerHTML) {\n        hasChildrenOverride = true;\n        push(props.innerHTML as string);\n      } else if (props.textContent) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.textContent as string));\n      } else if (tag === \"textarea\" && props.value) {\n        hasChildrenOverride = true;\n        push(escapeHtml(props.value as string));\n      }\n    }\n    if (!hasChildrenOverride) {\n      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {\n        push(escapeHtml(children as string));\n      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {\n        renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent);\n      }\n    }\n    push(`</${tag}>`);\n  }\n}\n\nfunction applySSRDirectives(\n  vnode: VNode,\n  rawProps: VNodeProps | null,\n  dirs: DirectiveBinding[],\n): VNodeProps {\n  const toMerge: VNodeProps[] = [];\n  for (let i = 0; i < dirs.length; i++) {\n    const binding = dirs[i];\n    const {\n      dir: { getSSRProps },\n    } = binding as any;\n    if (getSSRProps) {\n      const props = getSSRProps(binding, vnode);\n      if (props) toMerge.push(props);\n    }\n  }\n  return mergeProps(rawProps || {}, ...toMerge);\n}\n\nfunction renderTeleportVNode(\n  push: PushFn,\n  vnode: VNode,\n  parentComponent: ComponentInternalInstance,\n): void {\n  const target = vnode.props && vnode.props.to;\n  const disabled = vnode.props && vnode.props.disabled;\n\n  if (!target) {\n    if (!disabled) {\n      console.warn(`Teleport is missing target prop.`);\n    }\n    return;\n  }\n\n  if (!isString(target)) {\n    console.warn(`Teleport target must be a query selector string.`);\n    return;\n  }\n\n  // For disabled teleport, render children inline\n  if (disabled) {\n    renderVNodeChildren(push, vnode.children as VNodeArrayChildren, parentComponent);\n  } else {\n    // For enabled teleport, we render a placeholder comment\n    push(`<!--teleport start-->`);\n    push(`<!--teleport end-->`);\n  }\n}\n"
  },
  {
    "path": "impl/server-renderer/src/renderToString.ts",
    "content": "import { type App, type VNode, createVNode, isVNode } from \"@chibivue/runtime-core\";\nimport { isPromise, isString } from \"@chibivue/shared\";\nimport { type SSRBuffer, type SSRContext, renderComponentVNode } from \"./render\";\n\nfunction nestedUnrollBuffer(\n  buffer: SSRBuffer,\n  parentRet: string,\n  startIndex: number,\n): Promise<string> | string {\n  if (!buffer.hasAsync) {\n    return parentRet + unrollBufferSync(buffer);\n  }\n\n  let ret = parentRet;\n  for (let i = startIndex; i < buffer.length; i += 1) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n      continue;\n    }\n\n    if (isPromise(item)) {\n      return item.then((nestedItem) => {\n        buffer[i] = nestedItem;\n        return nestedUnrollBuffer(buffer, ret, i);\n      });\n    }\n\n    const result = nestedUnrollBuffer(item, ret, 0);\n    if (isPromise(result)) {\n      return result.then((nestedItem) => {\n        buffer[i] = nestedItem as any;\n        return nestedUnrollBuffer(buffer, \"\", i);\n      });\n    }\n\n    ret = result;\n  }\n\n  return ret;\n}\n\nexport function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {\n  return nestedUnrollBuffer(buffer, \"\", 0);\n}\n\nfunction unrollBufferSync(buffer: SSRBuffer): string {\n  let ret = \"\";\n  for (let i = 0; i < buffer.length; i++) {\n    const item = buffer[i];\n    if (isString(item)) {\n      ret += item;\n    } else {\n      ret += unrollBufferSync(item as SSRBuffer);\n    }\n  }\n  return ret;\n}\n\nexport async function renderToString(\n  input: App | VNode,\n  context: SSRContext = {},\n): Promise<string> {\n  if (isVNode(input)) {\n    // raw vnode, wrap with app\n    const vnode = input;\n    const buffer = await renderComponentVNode(createVNode({ render: () => vnode }), null);\n    return unrollBuffer(buffer as SSRBuffer) as Promise<string>;\n  }\n\n  // rendering an app\n  const app = input;\n  const vnode = createVNode(app._component, app._props);\n  vnode.appContext = app._context;\n\n  const buffer = await renderComponentVNode(vnode);\n  const result = await unrollBuffer(buffer as SSRBuffer);\n\n  if (context.__watcherHandles) {\n    for (const unwatch of context.__watcherHandles) {\n      unwatch();\n    }\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "impl/server-renderer/src/renderVapor.ts",
    "content": "import type { VNode } from \"@chibivue/runtime-core\";\nimport { isFunction } from \"@chibivue/shared\";\n\nimport type { VaporComponent, VaporComponentInternalInstance } from \"@chibivue/runtime-vapor\";\nimport { createVaporComponentInstance } from \"@chibivue/runtime-vapor\";\n\nimport { escapeHtml } from \"./helpers/ssrUtils\";\nimport { type SSRBuffer, createBuffer } from \"./render\";\n\nexport function renderVaporComponentToString(\n  vnode: VNode,\n  parentInstance: VaporComponentInternalInstance | null = null,\n): SSRBuffer | Promise<SSRBuffer> {\n  const instance = createVaporComponentInstance(vnode, parentInstance);\n  return renderVaporComponentSubTree(instance);\n}\n\nfunction renderVaporComponentSubTree(instance: VaporComponentInternalInstance): SSRBuffer {\n  const { getBuffer, push } = createBuffer();\n  const comp = instance.type as VaporComponent;\n\n  if (isFunction(comp)) {\n    // In SSR context, we need to mock the DOM APIs\n    // For vapor, we generate the template HTML directly\n    try {\n      // Create a mock vapor context for SSR\n      const ssrContext = createSSRVaporContext(push);\n\n      // Execute the vapor component with SSR context\n      // The component function expects to return a VaporNode (Element)\n      // In SSR, we intercept the template() call and generate HTML\n      const originalTemplate = globalThis.document;\n\n      // Set up SSR vapor globals\n      setupSSRVaporGlobals(ssrContext);\n\n      // Call the component - it will use our mocked globals\n      comp(instance);\n\n      // Get the rendered HTML\n      push(ssrContext.getHTML());\n\n      // Restore globals\n      restoreVaporGlobals();\n    } catch (e) {\n      // Fallback: render a placeholder\n      console.warn(`Vapor SSR render failed:`, e);\n      push(`<!---->`);\n    }\n  } else {\n    push(`<!---->`);\n  }\n\n  return getBuffer();\n}\n\ninterface SSRVaporContext {\n  html: string;\n  push: (item: string) => void;\n  getHTML: () => string;\n}\n\nfunction createSSRVaporContext(push: (item: string) => void): SSRVaporContext {\n  let html = \"\";\n  return {\n    html,\n    push,\n    getHTML: () => html,\n  };\n}\n\n// Mock DOM element for SSR\nclass SSRElement {\n  tagName: string;\n  attributes: Map<string, string> = new Map();\n  children: (SSRElement | SSRText)[] = [];\n  textContent: string = \"\";\n  parentElement: SSRElement | null = null;\n  __is_vapor: boolean = true;\n\n  constructor(tagName: string) {\n    this.tagName = tagName.toLowerCase();\n  }\n\n  get firstChild(): SSRElement | SSRText | null {\n    return this.children[0] || null;\n  }\n\n  get firstElementChild(): SSRElement | null {\n    return (this.children.find((c) => c instanceof SSRElement) as SSRElement) || null;\n  }\n\n  get childNodes(): (SSRElement | SSRText)[] {\n    return this.children;\n  }\n\n  setAttribute(name: string, value: string): void {\n    this.attributes.set(name, value);\n  }\n\n  getAttribute(name: string): string | null {\n    return this.attributes.get(name) || null;\n  }\n\n  addEventListener(): void {\n    // No-op in SSR\n  }\n\n  appendChild(child: SSRElement | SSRText): void {\n    child.parentElement = this;\n    this.children.push(child);\n  }\n\n  toHTML(): string {\n    const voidTags = [\n      \"br\",\n      \"hr\",\n      \"img\",\n      \"input\",\n      \"meta\",\n      \"link\",\n      \"area\",\n      \"base\",\n      \"col\",\n      \"embed\",\n      \"source\",\n      \"track\",\n      \"wbr\",\n    ];\n\n    let html = `<${this.tagName}`;\n\n    for (const [name, value] of this.attributes) {\n      if (value === \"\") {\n        html += ` ${name}`;\n      } else {\n        html += ` ${name}=\"${escapeHtml(value)}\"`;\n      }\n    }\n\n    html += \">\";\n\n    if (!voidTags.includes(this.tagName)) {\n      if (this.textContent && this.children.length === 0) {\n        html += escapeHtml(this.textContent);\n      } else {\n        for (const child of this.children) {\n          html += child.toHTML();\n        }\n      }\n      html += `</${this.tagName}>`;\n    }\n\n    return html;\n  }\n}\n\nclass SSRText {\n  textContent: string;\n  parentElement: SSRElement | null = null;\n\n  constructor(text: string) {\n    this.textContent = text;\n  }\n\n  toHTML(): string {\n    return escapeHtml(this.textContent);\n  }\n}\n\n// SSR mock document\nclass SSRDocument {\n  createElement(tagName: string): SSRElement {\n    return new SSRElement(tagName);\n  }\n\n  createTextNode(text: string): SSRText {\n    return new SSRText(text);\n  }\n\n  createComment(text: string): SSRText {\n    return new SSRText(`<!--${text}-->`);\n  }\n}\n\nlet originalDocument: any;\nlet ssrDocument: SSRDocument;\n\nfunction setupSSRVaporGlobals(context: SSRVaporContext): void {\n  if (typeof globalThis.document !== \"undefined\") {\n    originalDocument = globalThis.document;\n  }\n  ssrDocument = new SSRDocument();\n  (globalThis as any).document = ssrDocument;\n}\n\nfunction restoreVaporGlobals(): void {\n  if (originalDocument) {\n    (globalThis as any).document = originalDocument;\n  }\n}\n\n// Simple vapor SSR template helper\nexport function ssrVaporTemplate(html: string): string {\n  return html;\n}\n\n// Simple vapor SSR setText helper\nexport function ssrVaporSetText(format: string, ...values: any[]): string {\n  let text = format;\n  for (let i = 0; i < values.length; i++) {\n    text = text.replace(\"{}\", String(values[i]));\n  }\n  return escapeHtml(text);\n}\n"
  },
  {
    "path": "impl/server-renderer/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\"\n  },\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "impl/shared/package.json",
    "content": "{\n  \"name\": \"@chibivue/shared\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"author\": \"ubugeeei <ubuge1122@gmail.com>\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "impl/shared/src/domAttrConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n/**\n * On the client we only need to offer special cases for boolean attributes that\n * have different names from their corresponding dom properties:\n * - itemscope -> N/A\n * - allowfullscreen -> allowFullscreen\n * - formnovalidate -> formNoValidate\n * - ismap -> isMap\n * - nomodule -> noModule\n * - novalidate -> noValidate\n * - readonly -> readOnly\n */\nconst specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;\nexport const isSpecialBooleanAttr: (key: string) => boolean =\n  /*@__PURE__*/ makeMap(specialBooleanAttrs);\n\n/**\n * The full list is needed during SSR to produce the correct initial markup.\n */\nexport const isBooleanAttr: (key: string) => boolean = /*@__PURE__*/ makeMap(\n  specialBooleanAttrs +\n    `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` +\n    `inert,loop,open,required,reversed,scoped,seamless,` +\n    `checked,muted,multiple,selected`,\n);\n\n/**\n * Boolean attributes should be included if the value is truthy or ''.\n * e.g. `<select multiple>` compiles to `{ multiple: '' }`\n */\nexport function includeBooleanAttr(value: unknown): boolean {\n  return !!value || value === \"\";\n}\n\nconst unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\nconst attrValidationCache: Record<string, boolean> = {};\n\nexport function isSSRSafeAttrName(name: string): boolean {\n  if (attrValidationCache.hasOwnProperty(name)) {\n    return attrValidationCache[name];\n  }\n  const isUnsafe = unsafeAttrCharRE.test(name);\n  if (isUnsafe) {\n    console.error(`unsafe attribute name: ${name}`);\n  }\n  return (attrValidationCache[name] = !isUnsafe);\n}\n\nexport const propsToAttrMap: Record<string, string | undefined> = {\n  acceptCharset: \"accept-charset\",\n  className: \"class\",\n  htmlFor: \"for\",\n  httpEquiv: \"http-equiv\",\n};\n"
  },
  {
    "path": "impl/shared/src/domTagConfig.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst HTML_TAGS =\n  \"html,body,base,head,link,meta,style,title,address,article,aside,footer,\" +\n  \"header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,\" +\n  \"figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,\" +\n  \"data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,\" +\n  \"time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,\" +\n  \"canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,\" +\n  \"th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,\" +\n  \"option,output,progress,select,textarea,details,dialog,menu,\" +\n  \"summary,template,blockquote,iframe,tfoot\";\n\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n\nexport const isHTMLTag: (key: string) => boolean = /*#__PURE__*/ makeMap(HTML_TAGS);\nexport const isVoidTag: (key: string) => boolean = /*#__PURE__*/ makeMap(VOID_TAGS);\n"
  },
  {
    "path": "impl/shared/src/escapeHtml.ts",
    "content": "const escapeRE = /[\"'&<>]/;\n\nexport function escapeHtml(string: unknown): string {\n  const str = \"\" + string;\n  const match = escapeRE.exec(str);\n\n  if (!match) {\n    return str;\n  }\n\n  let html = \"\";\n  let escaped: string;\n  let index: number;\n  let lastIndex = 0;\n  for (index = match.index; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escaped = \"&quot;\";\n        break;\n      case 38: // &\n        escaped = \"&amp;\";\n        break;\n      case 39: // '\n        escaped = \"&#39;\";\n        break;\n      case 60: // <\n        escaped = \"&lt;\";\n        break;\n      case 62: // >\n        escaped = \"&gt;\";\n        break;\n      default:\n        continue;\n    }\n\n    if (lastIndex !== index) {\n      html += str.slice(lastIndex, index);\n    }\n\n    lastIndex = index + 1;\n    html += escaped;\n  }\n\n  return lastIndex !== index ? html + str.slice(lastIndex, index) : html;\n}\n\n// https://www.w3.org/TR/html52/syntax.html#comments\nconst commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g;\n\nexport function escapeHtmlComment(src: string): string {\n  return src.replace(commentStripRE, \"\");\n}\n"
  },
  {
    "path": "impl/shared/src/index.ts",
    "content": "import { makeMap } from \"./makeMap\";\n\nexport * from \"./toDisplayString\";\nexport * from \"./typeUtils\";\nexport * from \"./normalizeProp\";\nexport * from \"./shapeFlags\";\nexport * from \"./patchFlags\";\nexport * from \"./makeMap\";\nexport * from \"./domTagConfig\";\nexport * from \"./domAttrConfig\";\nexport * from \"./escapeHtml\";\n\nconst onRE = /^on[^a-z]/;\nexport const isOn = (key: string): boolean => onRE.test(key);\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>\n  hasOwnProperty.call(val, key);\n\nexport const isArray: typeof Array.isArray = Array.isArray;\nexport const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === \"[object Map]\";\nexport const isSet = (val: unknown): val is Set<any> => toTypeString(val) === \"[object Set]\";\n\nexport const isFunction = (val: unknown): val is Function => typeof val === \"function\";\nexport const isString = (val: unknown): val is string => typeof val === \"string\";\nexport const isSymbol = (val: unknown): val is symbol => typeof val === \"symbol\";\nexport const isObject = (val: unknown): val is Record<any, any> =>\n  val !== null && typeof val === \"object\";\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return (\n    (isObject(val) || isFunction(val)) &&\n    isFunction((val as any).then) &&\n    isFunction((val as any).catch)\n  );\n};\n\nexport const objectToString: typeof Object.prototype.toString = Object.prototype.toString;\nexport const toTypeString = (value: unknown): string => objectToString.call(value);\n\nexport const toRawType = (value: unknown): string => {\n  return toTypeString(value).slice(8, -1);\n};\n\nexport const isPlainObject = (val: unknown): val is object =>\n  toTypeString(val) === \"[object Object]\";\n\nexport const isIntegerKey = (key: unknown): boolean =>\n  isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n\nconst camelizeRE = /-(\\w)/g;\nexport const camelize = (str: string): string => {\n  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nconst hyphenateRE = /\\B([A-Z])/g;\nexport const hyphenate = (str: string): string => str.replace(hyphenateRE, \"-$1\").toLowerCase();\n\nexport const capitalize = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const toHandlerKey = (str: string): string => (str ? `on${capitalize(str)}` : ``);\n\nexport const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue);\n\nexport const invokeArrayFns = (fns: Function[], arg?: any): void => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](arg);\n  }\n};\n\nexport const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(\n  // the leading comma is intentional so empty string \"\" is also included\n  \",key,ref,ref_for,ref_key,\" +\n    \"onVnodeBeforeMount,onVnodeMounted,\" +\n    \"onVnodeBeforeUpdate,onVnodeUpdated,\" +\n    \"onVnodeBeforeUnmount,onVnodeUnmounted\",\n);\n\nexport function genPropsAccessExp(name: string): string {\n  return `__props.${name}`;\n}\n"
  },
  {
    "path": "impl/shared/src/makeMap.ts",
    "content": "export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) => boolean {\n  const map: Record<string, boolean> = Object.create(null);\n  const list: Array<string> = str.split(\",\");\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true;\n  }\n  return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n}\n"
  },
  {
    "path": "impl/shared/src/normalizeProp.ts",
    "content": "import { isArray, isObject, isString } from \".\";\n\nexport type NormalizedStyle = Record<string, string | number>;\n\nexport function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {\n  if (isArray(value)) {\n    const res: NormalizedStyle = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item)\n        ? parseStringStyle(item)\n        : (normalizeStyle(item) as NormalizedStyle);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\n\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\n\nexport function parseStringStyle(cssText: string): NormalizedStyle {\n  const ret: NormalizedStyle = {};\n  cssText\n    .replace(styleCommentRE, \"\")\n    .split(listDelimiterRE)\n    .forEach((item) => {\n      if (item) {\n        const tmp = item.split(propertyDelimiterRE);\n        tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n  return ret;\n}\n\nexport function normalizeClass(value: unknown): string {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\n\nexport function normalizeProps(props: Record<string, any> | null): Record<string, any> | null {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n"
  },
  {
    "path": "impl/shared/src/patchFlags.ts",
    "content": "/**\n * Patch flags are optimization hints generated by the compiler.\n * when a block with dynamicChildren is encountered during diff, the algorithm\n * enters \"optimized mode\". In this mode, we know that the vdom is produced by\n * a render function generated by the compiler, so the algorithm only needs to\n * handle updates explicitly marked by these patch flags.\n *\n * Patch flags can be combined using the | bitwise operator and can be checked\n * using the & operator, e.g.\n *\n * ```js\n * const flag = TEXT | CLASS\n * if (flag & TEXT) { ... }\n * ```\n */\nexport const enum PatchFlags {\n  /**\n   * Indicates an element with dynamic textContent (children fast path)\n   */\n  TEXT = 1,\n\n  /**\n   * Indicates an element with dynamic class binding.\n   */\n  CLASS = 1 << 1,\n\n  /**\n   * Indicates an element with dynamic style\n   */\n  STYLE = 1 << 2,\n\n  /**\n   * Indicates an element that has non-class/style dynamic props.\n   */\n  PROPS = 1 << 3,\n\n  /**\n   * Indicates an element with props with dynamic keys.\n   */\n  FULL_PROPS = 1 << 4,\n\n  /**\n   * Indicates an element that requires props hydration\n   */\n  NEED_HYDRATION = 1 << 5,\n\n  /**\n   * Indicates a fragment whose children order doesn't change.\n   */\n  STABLE_FRAGMENT = 1 << 6,\n\n  /**\n   * Indicates a fragment with keyed or partially keyed children\n   */\n  KEYED_FRAGMENT = 1 << 7,\n\n  /**\n   * Indicates a fragment with unkeyed children.\n   */\n  UNKEYED_FRAGMENT = 1 << 8,\n\n  /**\n   * Indicates an element that only needs non-props patching, e.g. ref or\n   * directives (onVnodeXXX hooks).\n   */\n  NEED_PATCH = 1 << 9,\n\n  /**\n   * Indicates a component with dynamic slots.\n   */\n  DYNAMIC_SLOTS = 1 << 10,\n\n  /**\n   * Indicates a fragment that was created only because the user has placed\n   * comments at the root level of a template. This is a dev-only flag.\n   */\n  DEV_ROOT_FRAGMENT = 1 << 11,\n\n  /**\n   * SPECIAL FLAGS -------------------------------------------------------------\n   * Special flags are negative integers. They are never matched against using\n   * bitwise operators, and are mutually exclusive.\n   */\n\n  /**\n   * Indicates a cached static vnode. This is also a hint for hydration to skip\n   * the entire sub tree since static content never needs to be updated.\n   */\n  CACHED = -1,\n\n  /**\n   * A special flag that indicates that the diffing algorithm should bail out\n   * of optimized mode.\n   */\n  BAIL = -2,\n}\n\n/**\n * dev only flag -> name mapping\n */\nexport const PatchFlagNames: Record<PatchFlags, string> = {\n  [PatchFlags.TEXT]: `TEXT`,\n  [PatchFlags.CLASS]: `CLASS`,\n  [PatchFlags.STYLE]: `STYLE`,\n  [PatchFlags.PROPS]: `PROPS`,\n  [PatchFlags.FULL_PROPS]: `FULL_PROPS`,\n  [PatchFlags.NEED_HYDRATION]: `NEED_HYDRATION`,\n  [PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,\n  [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,\n  [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,\n  [PatchFlags.NEED_PATCH]: `NEED_PATCH`,\n  [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,\n  [PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,\n  [PatchFlags.CACHED]: `CACHED`,\n  [PatchFlags.BAIL]: `BAIL`,\n};\n"
  },
  {
    "path": "impl/shared/src/shapeFlags.ts",
    "content": "export const enum ShapeFlags {\n  ELEMENT = 1,\n  FUNCTIONAL_COMPONENT = 1 << 1,\n  STATEFUL_COMPONENT = 1 << 2,\n  TEXT_CHILDREN = 1 << 3,\n  ARRAY_CHILDREN = 1 << 4,\n  SLOTS_CHILDREN = 1 << 5,\n  TELEPORT = 1 << 6,\n  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 7,\n  COMPONENT_KEPT_ALIVE = 1 << 8,\n  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,\n}\n"
  },
  {
    "path": "impl/shared/src/toDisplayString.ts",
    "content": "import { isArray, isFunction, isObject, isPlainObject, isString, objectToString } from \".\";\n\nexport const toDisplayString = (val: unknown): string => {\n  return isString(val)\n    ? val\n    : val == null\n      ? \"\"\n      : isArray(val) ||\n          (isObject(val) && (val.toString === objectToString || !isFunction(val.toString)))\n        ? JSON.stringify(val, replacer, 2)\n        : String(val);\n};\n\nconst replacer = (_key: string, val: any): any => {\n  // can't use isRef here since @vue/shared has no deps\n  if (val && val.__v_isRef) {\n    return replacer(_key, val.value);\n  } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {\n    return String(val);\n  }\n  return val;\n};\n"
  },
  {
    "path": "impl/shared/src/typeUtils.ts",
    "content": "export type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I,\n) => void\n  ? I\n  : never;\n\nexport type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"chibivue\",\n  \"version\": \"1.1.0\",\n  \"type\": \"module\",\n  \"author\": \"ubugeeei\",\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">=24\"\n  },\n  \"packageManager\": \"pnpm@10.28.0\",\n  \"scripts\": {\n    \"dev\": \"vitepress dev book/online-book\",\n    \"build\": \"vitepress build book/online-book\",\n    \"preview\": \"vitepress preview book/online-book\",\n    \"lint\": \"oxlint -c .oxlintrc.json .\",\n    \"lint:fix\": \"oxlint -c .oxlintrc.json --fix .\",\n    \"lint:text\": \"textlint book\",\n    \"fmt\": \"oxfmt .\",\n    \"fmt:check\": \"oxfmt --check .\",\n    \"check\": \"tsgo --noEmit\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"play\": \"pnpm --filter @chibivue/book-playground run generate && pnpm --filter @chibivue/book-playground run dev\",\n    \"//\": \"\",\n    \"setup\": \"pnpm i && pnpm setup:dev\",\n    \"setup:dev\": \"node --experimental-strip-types tools/chibivue-playground/main.ts\",\n    \"setup:vue\": \"node --experimental-strip-types tools/vue-playground/main.ts\",\n    \"setup:book\": \"node --experimental-strip-types tools/create-chibivue/main.ts\",\n    \"count-chars\": \"node --experimental-strip-types tools/book-size/book/count-chars.ts\",\n    \"translate\": \"node --experimental-strip-types tools/translator/ja2en/main.ts\",\n    \"impl:dev\": \"pnpm -F=@examples/playground run dev\",\n    \"impl:dev:app\": \"pnpm -F=@examples/app run dev\",\n    \"impl:dev:vapor\": \"pnpm -F=@examples/vapor run dev\",\n    \"impl:dev:vue\": \"cd examples/vuejs-core && pnpm i && pnpm run dev\",\n    \"impl:build\": \"rolldown -c\",\n    \"impl:clean\": \"rimraf impl/*/dist impl/@extensions/*/dist\",\n    \"impl:check\": \"pnpm lint && pnpm fmt:check && pnpm check && pnpm impl:build && pnpm test\",\n    \"impl:size\": \"tokei -f impl > tools/book-size/pkg/files.txt\"\n  },\n  \"devDependencies\": {\n    \"@babel/types\": \"^7.28.6\",\n    \"@iconify-json/mdi\": \"^1.2.3\",\n    \"@types/node\": \"^24.10.9\",\n    \"@typescript/native-preview\": \"^7.0.0-dev.20260117.1\",\n    \"@vitejs/plugin-vue\": \"^6.0.5\",\n    \"@vitest/browser\": \"^4.1.0\",\n    \"@vitest/browser-playwright\": \"^4.1.0\",\n    \"chainsi\": \"^0.0.1\",\n    \"citty\": \"^0.1.6\",\n    \"consola\": \"^3.4.2\",\n    \"dotenv\": \"^16.6.1\",\n    \"fs-extra\": \"^11.3.3\",\n    \"jsdom\": \"^26.1.0\",\n    \"oxc-minify\": \"^0.109.0\",\n    \"oxfmt\": \"^0.16.0\",\n    \"oxlint\": \"^1.39.0\",\n    \"playwright\": \"^1.52.0\",\n    \"rimraf\": \"^6.1.2\",\n    \"rolldown\": \"^1.0.0-beta.60\",\n    \"rolldown-plugin-dts\": \"^0.6.0\",\n    \"textlint\": \"^14.8.4\",\n    \"textlint-rule-ja-space-between-half-and-full-width\": \"^2.4.2\",\n    \"textlint-rule-prh\": \"^6.1.0\",\n    \"typescript\": \"^5.9.3\",\n    \"unplugin-icons\": \"^23.0.1\",\n    \"vite-hyper-config\": \"^0.8.1\",\n    \"vite-node\": \"^5.3.0\",\n    \"vitepress\": \"2.0.0-alpha.15\",\n    \"vitepress-plugin-mermaid\": \"^2.0.17\",\n    \"vitest\": \"^4.1.0\",\n    \"vue\": \"^3.5.26\"\n  },\n  \"dependencies\": {\n    \"@babel/parser\": \"^7.28.6\",\n    \"@monaco-editor/loader\": \"^1.7.0\",\n    \"@webcontainer/api\": \"^1.6.1\",\n    \"estree-walker\": \"^3.0.3\",\n    \"magic-string\": \"^0.30.21\",\n    \"monaco-editor\": \"^0.55.1\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"vitepress>vite\": \"^8.0.0\"\n    },\n    \"onlyBuiltDependencies\": [\n      \"esbuild\"\n    ]\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'impl/**'\n  - 'examples/*'\n  - 'book/playground'\n\n  # NOTE: auto generated by `nr setup:vue`\n  - '!examples/vuejs-core'\n"
  },
  {
    "path": "rolldown.config.ts",
    "content": "import { defineConfig } from \"rolldown\";\nimport { dts } from \"rolldown-plugin-dts\";\n\nconst PACKAGES = [\n  \"chibivue\",\n  \"compiler-core\",\n  \"compiler-dom\",\n  \"compiler-sfc\",\n  \"compiler-vapor\",\n  \"runtime-core\",\n  \"runtime-dom\",\n  \"runtime-vapor\",\n  \"server-renderer\",\n  \"reactivity\",\n  \"shared\",\n  \"@extensions/chibivue-router\",\n  \"@extensions/chibivue-store\",\n  \"@extensions/vite-plugin-chibivue\",\n];\n\nexport default defineConfig(\n  PACKAGES.map((pkg) => ({\n    input: `impl/${pkg}/src/index.ts`,\n    output: {\n      dir: `impl/${pkg}/dist`,\n      format: \"esm\",\n      entryFileNames: \"index.js\",\n    },\n    external: pkg === \"@extensions/vite-plugin-chibivue\" ? [\"vite\"] : [],\n    plugins: [dts()],\n  })),\n);\n"
  },
  {
    "path": "rules/prh-punctuation-mark.yml",
    "content": "version: 1\n\nrules:\n  - expected: ，\n    pattern:\n      - 、\n  - expected: ．\n    pattern:\n      - 。\n"
  },
  {
    "path": "tools/book-figures/generate.mjs",
    "content": "import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst outRoot = join(__dirname, \"../../book/online-book/src/public/figures\");\n\nconst brand = {\n  bg: \"#0f1724\",\n  bg2: \"#151e2d\",\n  panel: \"#1b2637\",\n  panel2: \"#22304a\",\n  mint: \"#2cc9a8\",\n  mint2: \"#8be4d3\",\n  navy: \"#1a2744\",\n  text: \"#e2ebf0\",\n  muted: \"#9aaac4\",\n  yellow: \"#f4d35e\",\n  orange: \"#e8a545\",\n  danger: \"#e85d75\",\n  line: \"#39516c\",\n};\n\nfunction esc(value) {\n  return String(value)\n    .replaceAll(\"&\", \"&amp;\")\n    .replaceAll(\"<\", \"&lt;\")\n    .replaceAll(\">\", \"&gt;\")\n    .replaceAll('\"', \"&quot;\");\n}\n\nfunction attrs(values) {\n  return Object.entries(values)\n    .filter(([, value]) => value !== undefined && value !== null && value !== false)\n    .map(([key, value]) => `${key}=\"${esc(value)}\"`)\n    .join(\" \");\n}\n\nfunction text(x, y, value, options = {}) {\n  const { className = \"label\", anchor = \"middle\", size, color, weight, lineHeight = 20 } = options;\n  const lines = String(value).split(\"\\n\");\n  const baseAttrs = attrs({\n    x,\n    y,\n    \"text-anchor\": anchor,\n    class: className,\n    \"font-size\": size,\n    fill: color,\n    \"font-weight\": weight,\n  });\n  if (lines.length === 1) return `<text ${baseAttrs}>${esc(value)}</text>`;\n  return `<text ${baseAttrs}>${lines\n    .map((line, index) => {\n      const dy = index === 0 ? 0 : lineHeight;\n      return `<tspan x=\"${x}\" dy=\"${dy}\">${esc(line)}</tspan>`;\n    })\n    .join(\"\")}</text>`;\n}\n\nfunction box(x, y, width, height, label, options = {}) {\n  const {\n    className = \"box\",\n    titleClass = \"box-label\",\n    sub,\n    accent = false,\n    muted = false,\n    dashed = false,\n  } = options;\n  const classes = [className, accent && \"accent\", muted && \"muted\", dashed && \"dashed\"]\n    .filter(Boolean)\n    .join(\" \");\n  const labelY = y + height / 2 + (sub ? -6 : 6);\n  return `<g>\n  <rect ${attrs({ x, y, width, height, rx: 8, ry: 8, class: classes })}/>\n  ${text(x + width / 2, labelY, label, { className: titleClass })}\n  ${sub ? text(x + width / 2, y + height / 2 + 20, sub, { className: \"sub-label\", lineHeight: 16 }) : \"\"}\n</g>`;\n}\n\nfunction lane(x, y, width, height, label) {\n  return `<g>\n  <rect ${attrs({ x, y, width, height, rx: 8, ry: 8, class: \"lane\" })}/>\n  ${text(x + 24, y + 34, label, { className: \"lane-label\", anchor: \"start\" })}\n</g>`;\n}\n\nfunction arrow(x1, y1, x2, y2, options = {}) {\n  const { label, dashed = false, color = brand.mint, bend } = options;\n  const classes = [\"arrow\", dashed && \"dashed\"].filter(Boolean).join(\" \");\n  const shape = bend\n    ? `<path ${attrs({ d: `M ${x1} ${y1} C ${bend[0]} ${bend[1]}, ${bend[2]} ${bend[3]}, ${x2} ${y2}`, class: classes, stroke: color })}/>`\n    : `<line ${attrs({ x1, y1, x2, y2, class: classes, stroke: color })}/>`;\n  const labelSvg = label\n    ? text((x1 + x2) / 2, (y1 + y2) / 2 - 10, label, { className: \"edge-label\" })\n    : \"\";\n  return `${shape}${labelSvg}`;\n}\n\nfunction pill(x, y, width, height, label, options = {}) {\n  return `<g>\n  <rect ${attrs({ x, y, width, height, rx: height / 2, ry: height / 2, class: options.accent ? \"pill accent\" : \"pill\" })}/>\n  ${text(x + width / 2, y + height / 2 + 6, label, { className: \"pill-label\" })}\n</g>`;\n}\n\nfunction codeChip(x, y, label, width = 180) {\n  return `<g>\n  <rect ${attrs({ x, y, width, height: 36, rx: 6, ry: 6, class: \"code-chip\" })}/>\n  ${text(x + width / 2, y + 23, label, { className: \"code-label\" })}\n</g>`;\n}\n\nfunction diagram({ width, height, title, subtitle, body }) {\n  return `<!-- Generated by tools/book-figures/generate.mjs. Do not edit directly. -->\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" role=\"img\" aria-labelledby=\"title desc\">\n  <title id=\"title\">${esc(title)}</title>\n  <desc id=\"desc\">${esc(subtitle || title)}</desc>\n  <defs>\n    <linearGradient id=\"bg\" x1=\"0\" x2=\"1\" y1=\"0\" y2=\"1\">\n      <stop offset=\"0\" stop-color=\"${brand.bg}\"/>\n      <stop offset=\"1\" stop-color=\"#0d121b\"/>\n    </linearGradient>\n    <linearGradient id=\"mintFill\" x1=\"0\" x2=\"1\" y1=\"0\" y2=\"1\">\n      <stop offset=\"0\" stop-color=\"${brand.mint}\" stop-opacity=\"0.28\"/>\n      <stop offset=\"1\" stop-color=\"${brand.mint2}\" stop-opacity=\"0.12\"/>\n    </linearGradient>\n    <marker id=\"arrowHead\" markerWidth=\"10\" markerHeight=\"10\" refX=\"8\" refY=\"5\" orient=\"auto\">\n      <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"${brand.mint}\"/>\n    </marker>\n  </defs>\n  <style>\n    text { font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; }\n    .canvas { fill: url(#bg); }\n    .title { fill: ${brand.text}; font-size: 24px; font-weight: 750; }\n    .subtitle { fill: ${brand.muted}; font-size: 13px; }\n    .lane { fill: ${brand.bg2}; stroke: ${brand.line}; stroke-width: 1.4; rx: 8; ry: 8; }\n    .lane-label { fill: ${brand.mint2}; font-size: 18px; font-weight: 700; }\n    .box { fill: ${brand.panel}; stroke: ${brand.line}; stroke-width: 1.4; rx: 8; ry: 8; }\n    .box.accent { fill: url(#mintFill); stroke: ${brand.mint}; stroke-width: 1.8; }\n    .box.muted { fill: #121a29; stroke: #2a3a50; }\n    .box.dashed { stroke-dasharray: 7 6; }\n    .box-label { fill: ${brand.text}; font-size: 16px; font-weight: 700; }\n    .sub-label { fill: ${brand.muted}; font-size: 12px; font-weight: 520; }\n    .label { fill: ${brand.text}; font-size: 15px; font-weight: 640; }\n    .muted-label { fill: ${brand.muted}; font-size: 13px; font-weight: 520; }\n    .edge-label { fill: ${brand.mint2}; font-size: 12px; font-weight: 720; }\n    .arrow { fill: none; stroke-width: 2.2; marker-end: url(#arrowHead); stroke-linecap: round; }\n    .arrow.dashed { stroke-dasharray: 7 6; }\n    .pill { fill: #121a29; stroke: ${brand.line}; stroke-width: 1.4; }\n    .pill.accent { fill: #163c42; stroke: ${brand.mint}; }\n    .pill-label { fill: ${brand.text}; font-size: 14px; font-weight: 720; }\n    .code-chip { fill: #0a0f14; stroke: #2a3a50; stroke-width: 1.2; }\n    .code-label { fill: ${brand.yellow}; font-size: 13px; font-family: \"SFMono-Regular\", Consolas, ui-monospace, monospace; font-weight: 650; }\n    .small { fill: ${brand.muted}; font-size: 12px; font-weight: 600; }\n    .warn { fill: ${brand.orange}; font-size: 13px; font-weight: 750; }\n    @media (prefers-reduced-motion: no-preference) {\n      .box.accent, .pill.accent {\n        animation: softPulse 6s ease-in-out infinite;\n      }\n      .arrow:not(.dashed) {\n        stroke-dasharray: 12 12;\n        animation: arrowFlow 14s linear infinite;\n      }\n    }\n    @keyframes softPulse {\n      0%, 100% { filter: drop-shadow(0 0 0 rgba(44, 201, 168, 0)); }\n      50% { filter: drop-shadow(0 0 8px rgba(44, 201, 168, 0.28)); }\n    }\n    @keyframes arrowFlow {\n      from { stroke-dashoffset: 0; }\n      to { stroke-dashoffset: -120; }\n    }\n  </style>\n  <rect class=\"canvas\" x=\"0\" y=\"0\" width=\"${width}\" height=\"${height}\" rx=\"8\"/>\n  ${text(32, 42, title, { className: \"title\", anchor: \"start\" })}\n  ${subtitle ? text(32, 66, subtitle, { className: \"subtitle\", anchor: \"start\" }) : \"\"}\n  ${body}\n</svg>\n`;\n}\n\nfunction keyedChildrenDiagram({ title, subtitle, oldItems, newItems, links, notes = [] }) {\n  const maxItems = Math.max(oldItems.length, newItems.length);\n  const notesY = 126 + maxItems * 58 + 34;\n  const height = notes.length > 0 ? notesY + notes.length * 22 + 38 : 450;\n  const row = (x, y, label, items, highlightIndex) => {\n    const cells = items\n      .map((item, index) => {\n        const cls = index === highlightIndex ? \"box accent\" : \"box\";\n        return `<rect ${attrs({ x, y: y + index * 58, width: 120, height: 42, rx: 8, ry: 8, class: cls })}/>\n${text(x + 60, y + index * 58 + 27, item, { className: \"box-label\" })}`;\n      })\n      .join(\"\\n\");\n    return `${text(x + 60, y - 18, label, { className: \"lane-label\" })}${cells}`;\n  };\n  const arrows = links\n    .map(([from, to, label, dashed]) =>\n      arrow(258, 150 + from * 58, 452, 150 + to * 58, {\n        label,\n        dashed,\n        color: dashed ? brand.orange : brand.mint,\n        bend: [320, 150 + from * 58, 390, 150 + to * 58],\n      }),\n    )\n    .join(\"\\n\");\n  return diagram({\n    width: 720,\n    height,\n    title,\n    subtitle,\n    body: `\n${row(138, 126, \"c1: old children\", oldItems)}\n${row(462, 126, \"c2: new children\", newItems)}\n${arrows}\n${notes\n  .map((note, index) =>\n    text(360, notesY + index * 22, note, { className: index === 0 ? \"warn\" : \"muted-label\" }),\n  )\n  .join(\"\\n\")}\n`,\n  });\n}\n\nconst diagrams = [\n  {\n    path: \"00-introduction/what-is-vue/vue-implementation-map.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 500,\n        title: \"Vue.js as an implementation map\",\n        subtitle:\n          \"The book follows the path from declarative UI to the runtime and compiler pieces behind it.\",\n        body: `\n${box(52, 130, 190, 78, \"Declarative UI\", { sub: \"template / render\" })}\n${box(286, 130, 180, 78, \"Component\", { sub: \"state + view\" })}\n${box(510, 130, 180, 78, \"Virtual DOM\", { sub: \"VNode tree\" })}\n${box(734, 130, 190, 78, \"Renderer\", { sub: \"patch DOM\" })}\n${arrow(242, 169, 286, 169)}\n${arrow(466, 169, 510, 169)}\n${arrow(690, 169, 734, 169)}\n${box(172, 292, 216, 72, \"Reactivity\", { sub: \"track -> trigger\", accent: true })}\n${box(592, 292, 216, 72, \"Template Compiler\", { sub: \"template -> render\", accent: true })}\n${arrow(280, 292, 370, 208, { label: \"updates\" })}\n${arrow(700, 292, 600, 208, { label: \"creates\" })}\n${pill(414, 398, 152, 42, \"chibivue\")}\n${arrow(490, 398, 490, 230, { label: \"build in layers\", dashed: true })}\n`,\n      }),\n  },\n  {\n    path: \"00-introduction/vue-core-components/package-dependency-overview.svg\",\n    svg: () =>\n      diagram({\n        width: 1100,\n        height: 660,\n        title: \"Vue package dependency map\",\n        subtitle:\n          \"Compiler packages prepare render functions; runtime packages mount and update the app.\",\n        body: `\n${lane(70, 110, 960, 250, \"Compiler packages\")}\n${lane(70, 410, 960, 152, \"Runtime packages\")}\n${box(128, 214, 214, 72, \"@vue/compiler-sfc\", { sub: \"SFC compiler\", accent: true })}\n${box(440, 156, 214, 72, \"@vue/compiler-dom\", { sub: \"DOM compiler\" })}\n${box(440, 278, 214, 72, \"@vue/compiler-core\", { sub: \"shared compiler\" })}\n${box(792, 260, 150, 72, \"vue\", { sub: \"public package\", accent: true })}\n${box(320, 452, 204, 64, \"@vue/runtime-dom\", { sub: \"browser runtime\" })}\n${box(608, 452, 204, 64, \"@vue/runtime-core\", { sub: \"renderer core\" })}\n${box(876, 452, 120, 64, \"@vue/reactivity\", { sub: \"effects\" })}\n${arrow(342, 242, 440, 192, { label: \"uses\" })}\n${arrow(342, 258, 440, 314, { label: \"uses\" })}\n${arrow(547, 228, 547, 278, { label: \"shares\" })}\n${arrow(792, 290, 654, 192, { label: \"compile\", bend: [744, 250, 706, 210] })}\n${arrow(792, 326, 524, 468, { label: \"runtime\", bend: [720, 392, 610, 424] })}\n${arrow(524, 484, 608, 484, { label: \"uses\" })}\n${arrow(812, 484, 876, 484, { label: \"tracks\" })}\n${codeChip(376, 594, \"template/SFC -> compiler -> render -> runtime\", 350)}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/package-architecture/runtime-core-dom-overview.svg\",\n    svg: () =>\n      diagram({\n        width: 1120,\n        height: 640,\n        title: \"runtime-core and runtime-dom responsibilities\",\n        subtitle:\n          \"runtime-core keeps pure rendering logic; runtime-dom injects browser-specific node operations.\",\n        body: `\n${lane(508, 96, 552, 190, \"runtime-core\")}\n${lane(420, 360, 640, 214, \"runtime-dom\")}\n${pill(60, 420, 130, 42, \"Developer\")}\n${box(244, 396, 196, 84, \"~/packages/index.ts\", { sub: \"public entry\" })}\n${box(536, 172, 188, 70, \"createAppAPI\", { sub: \"factory\" })}\n${box(816, 172, 188, 70, \"createRenderer\", { sub: \"factory\" })}\n${box(456, 454, 190, 72, \"runtime-dom/index.ts\", { sub: \"compose APIs\", accent: true })}\n${box(708, 454, 148, 72, \"render\", { sub: \"from core\" })}\n${box(900, 454, 126, 72, \"nodeOps\", { sub: \"DOM ops\" })}\n${arrow(190, 441, 244, 438)}\n${arrow(440, 438, 456, 486)}\n${arrow(548, 454, 618, 242, { label: \"create app\" })}\n${arrow(760, 454, 888, 242, { label: \"create renderer\" })}\n${arrow(900, 454, 908, 242, { label: \"inject options\" })}\n${arrow(646, 490, 708, 490)}\n${arrow(856, 490, 900, 490)}\n${codeChip(238, 520, \"createApp(App).mount()\", 260)}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/package-architecture/renderer-dependency-injection.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 470,\n        title: \"Renderer dependency injection\",\n        subtitle:\n          \"The renderer depends on a contract, then runtime-dom provides the concrete DOM operations.\",\n        body: `\n${box(76, 176, 204, 84, \"RendererOptions\", { sub: \"contract\", accent: true })}\n${box(398, 132, 230, 78, \"createRenderer(options)\", { sub: \"runtime-core\" })}\n${box(398, 270, 230, 78, \"render\", { sub: \"pure render logic\" })}\n${box(748, 176, 176, 84, \"nodeOps\", { sub: \"runtime-dom\" })}\n${arrow(748, 218, 628, 170, { label: \"inject\" })}\n${arrow(280, 218, 398, 170, { label: \"shape\" })}\n${arrow(512, 210, 512, 270)}\n${codeChip(70, 318, \"setElementText()\", 170)}\n${codeChip(748, 318, \"document.*\", 140)}\n${text(490, 408, \"DOM APIs stay outside runtime-core.\", { className: \"muted-label\" })}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/package-architecture/create-app-api-factory.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 500,\n        title: \"createAppAPI factory flow\",\n        subtitle: \"runtime-dom prepares render(), then runtime-core builds createApp() around it.\",\n        body: `\n${box(70, 170, 158, 70, \"nodeOps\", { sub: \"DOM ops\" })}\n${box(294, 170, 190, 70, \"createRenderer\", { sub: \"returns render\" })}\n${box(548, 170, 210, 70, \"createAppAPI\", { sub: \"accepts render\" })}\n${box(804, 170, 126, 70, \"createApp\", { sub: \"public API\", accent: true })}\n${arrow(228, 205, 294, 205)}\n${arrow(484, 205, 548, 205, { label: \"render\" })}\n${arrow(758, 205, 804, 205)}\n${box(210, 326, 204, 64, \"rootComponent\", { sub: \"user component\" })}\n${box(566, 326, 202, 64, \"app.mount()\", { sub: \"selector -> container\" })}\n${arrow(414, 358, 566, 358, { label: \"render message\" })}\n${arrow(866, 240, 670, 326, { label: \"returns app\", bend: [866, 286, 770, 296] })}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/reactivity/target-map-structure.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 540,\n        title: \"targetMap structure\",\n        subtitle:\n          \"A WeakMap connects each reactive target to keys, then keys to dependent effects.\",\n        body: `\n${box(60, 210, 170, 88, \"targetMap\", { sub: \"WeakMap<Target, KeyToDepMap>\", accent: true })}\n${box(328, 130, 150, 70, \"state1\", { sub: \"target\" })}\n${box(328, 320, 150, 70, \"state2\", { sub: \"target\" })}\n${box(560, 130, 148, 70, '\"name\"', { sub: \"key\" })}\n${box(560, 320, 148, 70, '\"count\"', { sub: \"key\" })}\n${box(790, 130, 140, 70, \"Dep\", { sub: \"updateComponent\" })}\n${box(790, 320, 140, 70, \"Dep\", { sub: \"onCountUpdated\" })}\n${arrow(230, 240, 328, 164, { label: \"target\" })}\n${arrow(230, 268, 328, 354, { label: \"target\" })}\n${arrow(478, 165, 560, 165, { label: \"key\" })}\n${arrow(478, 355, 560, 355, { label: \"key\" })}\n${arrow(708, 165, 790, 165, { label: \"effect\" })}\n${arrow(708, 355, 790, 355, { label: \"effect\" })}\n${codeChip(382, 442, \"state1.name -> updateComponent\", 300)}\n${codeChip(382, 486, \"state2.count -> onCountUpdated\", 320)}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/reactivity/reactive-track-trigger.svg\",\n    svg: () =>\n      diagram({\n        width: 1120,\n        height: 620,\n        title: \"reactive(), track(), and trigger()\",\n        subtitle: \"Proxy get registers the active effect; Proxy set finds it and runs it again.\",\n        body: `\n${box(60, 256, 150, 72, \"target\", { sub: \"plain object\" })}\n${box(278, 256, 150, 72, \"reactive()\", { sub: \"wrap\" })}\n${box(496, 256, 150, 72, \"Proxy\", { sub: \"get / set\", accent: true })}\n${arrow(210, 292, 278, 292)}\n${arrow(428, 292, 496, 292)}\n${box(730, 144, 140, 66, \"track()\", { sub: \"on get\" })}\n${box(730, 382, 140, 66, \"trigger()\", { sub: \"on set\" })}\n${box(890, 242, 160, 88, \"targetMap\", { sub: \"deps by target/key\", accent: true })}\n${box(890, 60, 160, 66, \"activeEffect\", { sub: \"current subscriber\" })}\n${box(890, 486, 160, 66, \"effect.run()\", { sub: \"fn()\" })}\n${arrow(646, 270, 730, 177, { label: \"get\" })}\n${arrow(646, 314, 730, 415, { label: \"set\" })}\n${arrow(870, 177, 890, 104, { label: \"read\" })}\n${arrow(870, 177, 890, 264, { label: \"register\" })}\n${arrow(870, 415, 890, 304, { label: \"lookup\" })}\n${arrow(970, 330, 970, 486, { label: \"run\" })}\n${text(352, 494, \"The Proxy is the gate where reads and writes become dependency events.\", { className: \"muted-label\" })}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/reactivity/reactivity-setup-flow.svg\",\n    svg: () =>\n      diagram({\n        width: 1120,\n        height: 720,\n        title: \"How reactivity is created during mount\",\n        subtitle: \"The first render collects dependencies; later writes use that collected map.\",\n        body: `\n${box(72, 140, 170, 70, \"setup()\", { sub: \"create state\" })}\n${box(310, 140, 190, 70, \"reactive state\", { sub: \"Proxy\" })}\n${box(568, 140, 230, 70, \"ReactiveEffect\", { sub: \"updateComponent\" })}\n${box(864, 140, 180, 70, \"effect.run()\", { sub: \"sets activeEffect\", accent: true })}\n${arrow(242, 175, 310, 175)}\n${arrow(500, 175, 568, 175)}\n${arrow(798, 175, 864, 175)}\n${box(864, 330, 180, 70, \"render()\", { sub: \"reads state.count\" })}\n${box(568, 330, 230, 70, \"getter\", { sub: \"track(target, key)\" })}\n${box(310, 330, 190, 70, \"targetMap\", { sub: \"state.count -> effect\", accent: true })}\n${arrow(954, 210, 954, 330)}\n${arrow(864, 365, 798, 365)}\n${arrow(568, 365, 500, 365)}\n${box(72, 510, 170, 70, \"increment()\", { sub: \"state.count++\" })}\n${box(310, 510, 190, 70, \"setter\", { sub: \"trigger(target, key)\" })}\n${box(568, 510, 230, 70, \"find effect\", { sub: \"from targetMap\" })}\n${box(864, 510, 180, 70, \"updateComponent\", { sub: \"patch screen\", accent: true })}\n${arrow(242, 545, 310, 545)}\n${arrow(500, 545, 568, 545)}\n${arrow(798, 545, 864, 545)}\n${arrow(954, 510, 954, 400, { label: \"next render\", dashed: true })}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/virtual-dom/patch-function-architecture.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 560,\n        title: \"patch function architecture\",\n        subtitle: \"patch dispatches by VNode type, then splits mount and update work.\",\n        body: `\n${box(390, 118, 200, 72, \"patch(n1, n2)\", { sub: \"entry point\", accent: true })}\n${box(164, 274, 190, 72, \"processText\", { sub: \"type === Text\" })}\n${box(626, 274, 190, 72, \"processElement\", { sub: \"element vnode\" })}\n${arrow(390, 154, 354, 274, { label: \"Text\" })}\n${arrow(590, 154, 626, 274, { label: \"Element\" })}\n${box(70, 422, 150, 60, \"mountText\", { sub: \"n1 == null\" })}\n${box(278, 422, 150, 60, \"patchText\", { sub: \"update\" })}\n${box(532, 422, 160, 60, \"mountElement\", { sub: \"n1 == null\" })}\n${box(754, 422, 160, 60, \"patchElement\", { sub: \"update\" })}\n${arrow(220, 346, 145, 422)}\n${arrow(298, 346, 353, 422)}\n${arrow(690, 346, 612, 422)}\n${arrow(770, 346, 834, 422)}\n`,\n      }),\n  },\n  {\n    path: \"10-minimum-example/minimum-component/component-instance-flow.svg\",\n    svg: () =>\n      diagram({\n        width: 1040,\n        height: 560,\n        title: \"Component instance flow\",\n        subtitle:\n          \"The renderer creates an instance so each component can keep its own render state.\",\n        body: `\n${box(62, 170, 190, 72, \"h(MyComponent)\", { sub: \"component VNode\" })}\n${box(322, 170, 214, 72, \"processComponent\", { sub: \"mount or update\" })}\n${box(616, 170, 220, 72, \"create instance\", { sub: \"ComponentInternalInstance\", accent: true })}\n${arrow(252, 206, 322, 206)}\n${arrow(536, 206, 616, 206)}\n${box(180, 360, 164, 64, \"setup()\", { sub: \"returns render\" })}\n${box(438, 360, 164, 64, \"subTree\", { sub: \"render result\" })}\n${box(696, 360, 164, 64, \"patch()\", { sub: \"DOM update\" })}\n${arrow(726, 242, 262, 360, { label: \"initialize\", bend: [700, 300, 420, 300] })}\n${arrow(344, 392, 438, 392)}\n${arrow(602, 392, 696, 392)}\n${codeChip(392, 476, \"instance.subTree / instance.update / instance.effect\", 360)}\n`,\n      }),\n  },\n  {\n    path: \"20-basic-virtual-dom/patch-keyed-children/same-length-index-patch.svg\",\n    svg: () =>\n      keyedChildrenDiagram({\n        title: \"Index-based patch with equal lengths\",\n        subtitle:\n          \"The first implementation works only when old and new child arrays line up by index.\",\n        oldItems: [\"li 1\", \"li 2\", \"li 3\", \"li 4\"],\n        newItems: [\"li 1\", \"li 2\", \"li 3\", \"li 4\"],\n        links: [\n          [0, 0, \"patch\", false],\n          [1, 1, \"patch\", false],\n          [2, 2, \"patch\", false],\n          [3, 3, \"patch\", false],\n        ],\n      }),\n  },\n  {\n    path: \"20-basic-virtual-dom/patch-keyed-children/deleted-child-bug.svg\",\n    svg: () =>\n      keyedChildrenDiagram({\n        title: \"Deleted child bug\",\n        subtitle: \"Looping over c2 leaves extra old nodes untouched.\",\n        oldItems: [\"li a\", \"li b\", \"li c\", \"li d\"],\n        newItems: [\"li e\", \"li f\", \"li g\"],\n        links: [\n          [0, 0, \"patch\", false],\n          [1, 1, \"patch\", false],\n          [2, 2, \"patch\", false],\n        ],\n        notes: [\"li d is never visited\", \"The DOM keeps a stale child.\"],\n      }),\n  },\n  {\n    path: \"20-basic-virtual-dom/patch-keyed-children/inserted-child-index-bug.svg\",\n    svg: () =>\n      keyedChildrenDiagram({\n        title: \"Inserted child with index matching\",\n        subtitle: \"A new item shifts positions, so index-based comparison pairs the wrong nodes.\",\n        oldItems: [\"li 1\", \"li 2\", \"li 3\", \"li 4\"],\n        newItems: [\"li 1\", \"new li\", \"li 2\", \"li 3\", \"li 4\"],\n        links: [\n          [0, 0, \"patch\", false],\n          [1, 1, \"wrong\", true],\n          [2, 2, \"wrong\", true],\n          [3, 3, \"wrong\", true],\n        ],\n        notes: [\n          \"The inserted node changes every later index.\",\n          \"Keys are needed to find identity.\",\n        ],\n      }),\n  },\n  {\n    path: \"20-basic-virtual-dom/patch-keyed-children/inserted-child-keyed-match.svg\",\n    svg: () =>\n      keyedChildrenDiagram({\n        title: \"Inserted child with keyed matching\",\n        subtitle: \"Keys let the renderer match old and new nodes by identity instead of position.\",\n        oldItems: [\"li 1\", \"li 2\", \"li 3\", \"li 4\"],\n        newItems: [\"li 1\", \"new li\", \"li 2\", \"li 3\", \"li 4\"],\n        links: [\n          [0, 0, \"key\", false],\n          [1, 2, \"key\", false],\n          [2, 3, \"key\", false],\n          [3, 4, \"key\", false],\n        ],\n        notes: [\"new li is mounted\", \"Existing nodes keep their identity.\"],\n      }),\n  },\n  {\n    path: \"20-basic-virtual-dom/bit-flags/shape-flag-overview.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 500,\n        title: \"ShapeFlags pack VNode shape into bits\",\n        subtitle: \"A renderer can check one number instead of repeating type checks.\",\n        body: `\n${box(78, 158, 210, 82, \"VNode\", { sub: \"type: 'div'\\nchildren: []\", accent: true })}\n${box(388, 126, 210, 58, \"ELEMENT\", { sub: \"1\" })}\n${box(388, 212, 210, 58, \"ARRAY_CHILDREN\", { sub: \"1 << 4\" })}\n${box(692, 158, 210, 82, \"shapeFlag\", { sub: \"0b0001_0001\", accent: true })}\n${arrow(288, 198, 388, 155)}\n${arrow(288, 198, 388, 241)}\n${arrow(598, 155, 692, 198)}\n${arrow(598, 241, 692, 198)}\n${codeChip(326, 344, \"vnode.shapeFlag & ShapeFlags.ELEMENT\", 350)}\n${codeChip(326, 394, \"vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN\", 390)}\n`,\n      }),\n  },\n  {\n    path: \"30-basic-reactivity-system/reactivity-optimization/push-pull-overview.svg\",\n    svg: () =>\n      diagram({\n        width: 1040,\n        height: 520,\n        title: \"Push-Pull reactivity overview\",\n        subtitle: \"Changes push dirty marks; reads pull the recalculation only when it is needed.\",\n        body: `\n${box(74, 170, 172, 70, \"signal changes\", { sub: \"trigger\" })}\n${box(322, 170, 180, 70, \"push phase\", { sub: \"mark dirty\", accent: true })}\n${box(578, 170, 180, 70, \"computed read\", { sub: \"getter\" })}\n${box(834, 170, 150, 70, \"pull phase\", { sub: \"recalculate if dirty\", accent: true })}\n${arrow(246, 205, 322, 205)}\n${arrow(502, 205, 578, 205)}\n${arrow(758, 205, 834, 205)}\n${box(176, 340, 236, 70, \"version check\", { sub: \"globalVersion / dep.version\" })}\n${box(620, 340, 236, 70, \"linked deps\", { sub: \"Dep <-> Subscriber\" })}\n${arrow(412, 375, 620, 375, { label: \"O(1) updates\", dashed: true })}\n${text(520, 462, \"The actual work is delayed until a value is read again.\", { className: \"muted-label\" })}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/transform/basic-compiler-pipeline.svg\",\n    svg: () =>\n      diagram({\n        width: 1040,\n        height: 430,\n        title: \"Minimum compiler pipeline\",\n        subtitle:\n          \"The early implementation parses the template and immediately generates a render function.\",\n        body: `\n${box(66, 178, 190, 70, \"source\", { sub: \"template string\" })}\n${box(318, 178, 160, 70, \"Parser\", { sub: \"baseParse\" })}\n${box(540, 178, 160, 70, \"AST\", { sub: \"template AST\", accent: true })}\n${box(762, 178, 190, 70, \"Codegen\", { sub: \"render function\" })}\n${arrow(256, 213, 318, 213)}\n${arrow(478, 213, 540, 213)}\n${arrow(700, 213, 762, 213)}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/transform/compiler-pipeline-with-transformer.svg\",\n    svg: () =>\n      diagram({\n        width: 1120,\n        height: 450,\n        title: \"Compiler pipeline with transformer\",\n        subtitle: \"transform() prepares codegen-oriented nodes before the code generator runs.\",\n        body: `\n${box(58, 188, 170, 70, \"source\", { sub: \"template\" })}\n${box(282, 188, 150, 70, \"Parser\", { sub: \"baseParse\" })}\n${box(486, 188, 150, 70, \"Template AST\", { sub: \"input focused\" })}\n${box(690, 188, 160, 70, \"Transformer\", { sub: \"transform()\", accent: true })}\n${box(904, 188, 160, 70, \"Codegen\", { sub: \"render()\" })}\n${arrow(228, 223, 282, 223)}\n${arrow(432, 223, 486, 223)}\n${arrow(636, 223, 690, 223)}\n${arrow(850, 223, 904, 223)}\n${box(628, 326, 284, 54, \"codegenNode / JS AST\", { sub: \"output focused\", muted: true })}\n${arrow(770, 258, 770, 326, { dashed: true })}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/transform/transform-type-relationships.svg\",\n    svg: () =>\n      diagram({\n        width: 1120,\n        height: 620,\n        title: \"Transform function types\",\n        subtitle:\n          \"NodeTransform and DirectiveTransform are function contracts with concrete transform implementations.\",\n        body: `\n${lane(64, 116, 992, 214, \"Node transforms\")}\n${lane(64, 374, 992, 154, \"Directive transforms\")}\n${box(116, 184, 220, 72, \"NodeTransform\", { sub: \"node -> onExit?\", accent: true })}\n${box(470, 146, 190, 58, \"transformElement\", { sub: \"ElementNode\" })}\n${box(470, 232, 190, 58, \"transformExpression\", { sub: \"ExpressionNode\" })}\n${box(770, 188, 190, 58, \"other node transforms\", { sub: \"implementation set\" })}\n${arrow(336, 210, 470, 175, { label: \"implements\" })}\n${arrow(336, 226, 470, 261, { label: \"implements\" })}\n${arrow(660, 175, 770, 208, { label: \"and\" })}\n${box(116, 416, 220, 72, \"DirectiveTransform\", { sub: \"directive -> props\", accent: true })}\n${box(470, 398, 150, 58, \"transformOn\", { sub: \"v-on\" })}\n${box(664, 398, 150, 58, \"transformFor\", { sub: \"v-for\" })}\n${box(858, 398, 150, 58, \"transformIf\", { sub: \"v-if\" })}\n${box(664, 484, 230, 58, \"other directive transforms\", { sub: \"implementation set\" })}\n${arrow(336, 442, 470, 427, { label: \"implements\" })}\n${arrow(620, 427, 664, 427)}\n${arrow(814, 427, 858, 427)}\n${arrow(336, 462, 664, 513, { label: \"and\", bend: [440, 520, 560, 520] })}\n${codeChip(382, 566, \"compiler-core/src/transforms\", 356)}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/v-bind/directive-node-shape.svg\",\n    svg: () =>\n      diagram({\n        width: 760,\n        height: 360,\n        title: \"DirectiveNode shape for v-bind\",\n        subtitle: \"v-bind needs both the argument and the expression as nodes.\",\n        body: `\n${box(72, 146, 180, 70, \"DirectiveNode\", { sub: \"name: bind\", accent: true })}\n${box(334, 90, 150, 58, \"name\", { sub: \"bind\" })}\n${box(334, 178, 150, 58, \"arg\", { sub: \"id / [key]\" })}\n${box(334, 266, 150, 58, \"exp\", { sub: \"count\" })}\n${box(560, 178, 140, 58, \"ExpressionNode\", { sub: \"isStatic?\" })}\n${arrow(252, 178, 334, 119)}\n${arrow(252, 182, 334, 207)}\n${arrow(252, 186, 334, 295)}\n${arrow(484, 207, 560, 207)}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/v-bind/transform-vbind-flow.svg\",\n    svg: () =>\n      diagram({\n        width: 1080,\n        height: 700,\n        title: \"v-bind transform flow\",\n        subtitle:\n          \"The transformer chooses the right codegen path from the argument and binding kind.\",\n        body: `\n${box(430, 104, 220, 64, \"DirectiveNode\", { sub: \"v-bind / :arg\", accent: true })}\n${box(430, 226, 220, 64, \"Has arg?\", { sub: \"arg exists?\" })}\n${arrow(540, 168, 540, 226)}\n${box(108, 348, 218, 64, \"No arg\", { sub: 'v-bind=\"obj\"' })}\n${box(430, 348, 220, 64, \"Static arg\", { sub: \":class / :style / :id\" })}\n${box(754, 348, 218, 64, \"Dynamic arg\", { sub: \":[key]\" })}\n${arrow(430, 258, 326, 348, { label: \"no\" })}\n${arrow(540, 290, 540, 348, { label: \"yes\" })}\n${arrow(650, 258, 754, 348, { label: \"dynamic\" })}\n${box(108, 500, 218, 64, \"mergeProps()\", { sub: \"object binding\", accent: true })}\n${box(430, 500, 220, 64, \"normalizeClass/Style\", { sub: \"if class or style\", accent: true })}\n${box(754, 500, 218, 64, \"normalizeProps()\", { sub: \"unknown key\", accent: true })}\n${arrow(217, 412, 217, 500)}\n${arrow(540, 412, 540, 500)}\n${arrow(863, 412, 863, 500)}\n${text(540, 638, \"Result: a Codegen property or helper call used by h(...).\", { className: \"muted-label\" })}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/event-modifier/compiler-architecture-core-only.svg\",\n    svg: () =>\n      diagram({\n        width: 960,\n        height: 560,\n        title: \"Compiler architecture before DOM modifiers\",\n        subtitle:\n          \"The v-on transform still lives in compiler-core, so it must stay DOM independent.\",\n        body: `\n${lane(80, 124, 800, 160, \"compiler-core\")}\n${box(132, 190, 150, 58, \"parse\", { sub: \"template AST\" })}\n${box(398, 190, 170, 58, \"transformOn\", { sub: \"@click -> onClick\", accent: true })}\n${box(688, 190, 140, 58, \"generate\", { sub: \"code\" })}\n${arrow(282, 219, 398, 219)}\n${arrow(568, 219, 688, 219)}\n${lane(80, 348, 800, 116, \"compiler-dom\")}\n${box(338, 386, 284, 54, \"uses compiler-core defaults\", { sub: \"no DOM-specific transform yet\" })}\n${arrow(480, 348, 480, 284, { label: \"delegates\", dashed: true })}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/event-modifier/compiler-architecture-dom-augmentor.svg\",\n    svg: () =>\n      diagram({\n        width: 980,\n        height: 600,\n        title: \"Compiler architecture with DOM augmentor\",\n        subtitle: \"compiler-dom wraps core transforms and injects DOM-specific modifier handling.\",\n        body: `\n${lane(86, 108, 808, 170, \"compiler-core\")}\n${box(134, 178, 170, 58, \"baseTransformOn\", { sub: \"DOM free\" })}\n${box(430, 178, 164, 58, \"augmentor\", { sub: \"callback slot\", accent: true })}\n${box(724, 178, 122, 58, \"result\", { sub: \"props\" })}\n${arrow(304, 207, 430, 207)}\n${arrow(594, 207, 724, 207)}\n${lane(86, 352, 808, 154, \"compiler-dom\")}\n${box(156, 404, 218, 58, \"transformOn\", { sub: \"wrap core\" })}\n${box(534, 404, 232, 58, \"withModifiers()\", { sub: \"prevent / stop\", accent: true })}\n${arrow(374, 433, 534, 433, { label: \"DOM logic\" })}\n${arrow(650, 404, 512, 236, { label: \"inject augmentor\", bend: [650, 320, 560, 300] })}\n${text(490, 552, \"compiler-core stays portable; compiler-dom owns browser event behavior.\", { className: \"muted-label\" })}\n`,\n      }),\n  },\n  {\n    path: \"50-basic-template-compiler/v-for/for-parse-result.svg\",\n    svg: () =>\n      diagram({\n        width: 760,\n        height: 380,\n        title: \"ForParseResult shape\",\n        subtitle: \"v-for decomposes the expression into source, value, key, and index.\",\n        body: `\n${codeChip(168, 106, 'v-for=\"(fruit, i) in fruits\"', 424)}\n${box(76, 212, 138, 58, \"source\", { sub: \"fruits\", accent: true })}\n${box(244, 212, 138, 58, \"value\", { sub: \"fruit\" })}\n${box(412, 212, 138, 58, \"key\", { sub: \"i\" })}\n${box(580, 212, 138, 58, \"index\", { sub: \"undefined\" })}\n${arrow(380, 142, 145, 212)}\n${arrow(380, 142, 313, 212)}\n${arrow(380, 142, 481, 212)}\n${arrow(380, 142, 649, 212)}\n`,\n      }),\n  },\n];\n\nfor (const item of diagrams) {\n  const file = join(outRoot, item.path);\n  mkdirSync(dirname(file), { recursive: true });\n  writeFileSync(file, item.svg(), \"utf8\");\n  console.log(`generated ${file}`);\n}\n"
  },
  {
    "path": "tools/book-size/book/char-counts.json",
    "content": "{ \"length\": 370442 }\n"
  },
  {
    "path": "tools/book-size/book/count-chars.ts",
    "content": "import fs from \"node:fs\";\n\nconst listFiles = (dir: string): string[] =>\n  fs\n    .readdirSync(dir, { withFileTypes: true })\n    .flatMap((dirent) =>\n      dirent.isFile() ? [`${dir}/${dirent.name}`] : listFiles(`${dir}/${dirent.name}`),\n    );\n\nconst countBookSize = (files: string[]): number =>\n  files.reduce((acc, file) => {\n    if (file.endsWith(\".md\")) {\n      const content = fs.readFileSync(file, \"utf-8\");\n      const size = content.length;\n      return acc + size;\n    }\n    return acc;\n  }, 0);\n\n(function main() {\n  const ROOT = \"book/online-book/src\";\n  const IGNORES = [\"en\", \"__wip\"];\n  const OUT = \"tools/book-size/book/char-counts.json\";\n  const files = listFiles(ROOT).filter(\n    (file) => !IGNORES.some((ignore) => file.split(\"/\").includes(ignore)),\n  );\n  const bookSize = countBookSize(files);\n  const json = JSON.stringify({ length: bookSize });\n  fs.writeFileSync(OUT, json);\n})();\n"
  },
  {
    "path": "tools/book-size/pkg/files.txt",
    "content": "=====================================================================================================================================================================================================================================================================\n Language                                                                                                                                                                                                  Files        Lines         Code     Comments       Blanks\n=====================================================================================================================================================================================================================================================================\n TypeScript                                                                                                                                                                                                   92         9023         7600          383         1040\n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n packages/@extensions/chibivue-router/types/index.ts                                                                                                                                                                        4            4            0            0\n packages/@extensions/vite-plugin-chibivue/script.ts                                                                                                                                                                       19           14            3            2\n packages/@extensions/chibivue-router/useApi.ts                                                                                                                                                                            12           10            0            2\n packages/@extensions/vite-plugin-chibivue/utils/query.ts                                                                                                                                                                  31           30            0            1\n packages/@extensions/chibivue-router/RouterView.ts                                                                                                                                                                        19           13            2            4\n packages/@extensions/vite-plugin-chibivue/utils/descriptorCache.ts                                                                                                                                                        61           50            1           10\n packages/@extensions/vite-plugin-chibivue/main.ts                                                                                                                                                                         85           65            3           17\n packages/@extensions/vite-plugin-chibivue/index.ts                                                                                                                                                                        42           35            1            6\n packages/@extensions/chibivue-router/history.ts                                                                                                                                                                           21           20            0            1\n packages/@extensions/chibivue-store/index.ts                                                                                                                                                                               2            2            0            0\n packages/@extensions/chibivue-store/createStore.ts                                                                                                                                                                        13           11            0            2\n packages/compiler-dom/runtimeHelpers.ts                                                                                                                                                                                    9            7            0            2\n packages/@extensions/chibivue-store/store.ts                                                                                                                                                                              36           31            0            5\n packages/@extensions/chibivue-store/rootStore.ts                                                                                                                                                                          14           10            0            4\n packages/@extensions/chibivue-router/router.ts                                                                                                                                                                            69           58            0           11\n packages/compiler-dom/transforms/vModel.ts                                                                                                                                                                                43           35            2            6\n packages/compiler-dom/parserOptions.ts                                                                                                                                                                                     6            5            0            1\n packages/compiler-dom/codegen.ts                                                                                                                                                                                           7            6            0            1\n packages/@extensions/chibivue-router/injectionSymbols.ts                                                                                                                                                                  12            9            0            3\n packages/runtime-core/helpers/renderList.ts                                                                                                                                                                                8            7            0            1\n packages/index.ts                                                                                                                                                                                                         18           15            0            3\n packages/runtime-core/helpers/resolveAssets.ts                                                                                                                                                                            36           27            2            7\n packages/compiler-dom/index.ts                                                                                                                                                                                            33           30            0            3\n packages/runtime-core/helpers/toHandlers.ts                                                                                                                                                                               12            8            3            1\n packages/runtime-core/enums.ts                                                                                                                                                                                             8            8            0            0\n packages/runtime-core/apiComputed.ts                                                                                                                                                                                       6            4            1            1\n packages/runtime-core/componentSlots.ts                                                                                                                                                                                   43           36            0            7\n packages/runtime-core/apiDefineComponent.ts                                                                                                                                                                               57           54            0            3\n packages/runtime-core/index.ts                                                                                                                                                                                            66           50            5           11\n packages/runtime-core/componentOptions.ts                                                                                                                                                                                310          271            2           37\n packages/@extensions/vite-plugin-chibivue/template.ts                                                                                                                                                                     20           18            0            2\n packages/runtime-core/apiLifecycle.ts                                                                                                                                                                                     35           32            0            3\n packages/runtime-core/componentEmits.ts                                                                                                                                                                                   38           32            0            6\n packages/runtime-core/componentRenderUtils.ts                                                                                                                                                                             12           11            0            1\n packages/runtime-core/apiInject.ts                                                                                                                                                                                        21           18            0            3\n packages/runtime-core/apiWatch.ts                                                                                                                                                                                        142          120            3           19\n packages/runtime-core/directives.ts                                                                                                                                                                                       88           79            0            9\n packages/runtime-core/componentPublicInstance.ts                                                                                                                                                                         132          124            0            8\n packages/runtime-core/rendererTemplateRef.ts                                                                                                                                                                              12           11            0            1\n packages/runtime-core/componentRenderContext.ts                                                                                                                                                                           11            9            0            2\n packages/runtime-core/scheduler.ts                                                                                                                                                                                       151          128            0           23\n packages/runtime-core/componentProps.ts                                                                                                                                                                                   52           44            2            6\n packages/runtime-core/apiCreateApp.ts                                                                                                                                                                                     72           59            1           12\n packages/runtime-core/vnode.ts                                                                                                                                                                                           337          299            8           30\n packages/runtime-core/h.ts                                                                                                                                                                                               109           95            8            6\n packages/reactivity/index.ts                                                                                                                                                                                               5            5            0            0\n packages/runtime-core/component.ts                                                                                                                                                                                       227          188            3           36\n packages/reactivity/dep.ts                                                                                                                                                                                                 8            6            0            2\n packages/reactivity/computed.ts                                                                                                                                                                                          109           86            5           18\n packages/reactivity/reactive.ts                                                                                                                                                                                          151          133            0           18\n packages/@extensions/chibivue-router/index.ts                                                                                                                                                                              3            3            0            0\n packages/shared/shapeFlags.ts                                                                                                                                                                                              7            7            0            0\n packages/shared/typeUtils.ts                                                                                                                                                                                               9            7            0            2\n packages/reactivity/effectScope.ts                                                                                                                                                                                        83           69            0           14\n packages/shared/makeMap.ts                                                                                                                                                                                                13           13            0            0\n packages/reactivity/effect.ts                                                                                                                                                                                            134          112            0           22\n packages/shared/normalizeProp.ts                                                                                                                                                                                          76           70            0            6\n packages/shared/domTagConfig.ts                                                                                                                                                                                           15           12            1            2\n packages/reactivity/collectionHandlers.ts                                                                                                                                                                                281          249            0           32\n packages/shared/index.ts                                                                                                                                                                                                  75           56            4           15\n packages/shared/toDisplayString.ts                                                                                                                                                                                        57           48            6            3\n packages/compiler-sfc/index.ts                                                                                                                                                                                             4            4            0            0\n packages/runtime-core/renderer.ts                                                                                                                                                                                        764          626           64           74\n packages/compiler-sfc/compileTemplate.ts                                                                                                                                                                                  37           33            0            4\n packages/compiler-sfc/rewriteDefault.ts                                                                                                                                                                                   99           89            1            9\n packages/reactivity/ref.ts                                                                                                                                                                                               272          202           35           35\n packages/runtime-dom/modules/style.ts                                                                                                                                                                                     46           42            1            3\n packages/compiler-sfc/parse.ts                                                                                                                                                                                           152          133            2           17\n packages/runtime-dom/modules/attrs.ts                                                                                                                                                                                      7            7            0            0\n packages/runtime-dom/modules/events.ts                                                                                                                                                                                    58           48            4            6\n packages/runtime-dom/index.ts                                                                                                                                                                                             37           29            2            6\n packages/runtime-dom/nodeOps.ts                                                                                                                                                                                           42           33            0            9\n packages/compiler-core/options.ts                                                                                                                                                                                         57           39            9            9\n packages/runtime-dom/directives/vModel.ts                                                                                                                                                                                 78           71            1            6\n packages/compiler-core/index.ts                                                                                                                                                                                           13            9            0            4\n packages/compiler-core/transforms/vModel.ts                                                                                                                                                                               36           28            3            5\n packages/runtime-dom/patchProp.ts                                                                                                                                                                                         25           20            3            2\n packages/compiler-core/transforms/vOn.ts                                                                                                                                                                                  83           71            3            9\n packages/compiler-core/runtimeHelpers.ts                                                                                                                                                                                  38           35            0            3\n packages/compiler-core/transforms/transformExpression.ts                                                                                                                                                                 158          137            4           17\n packages/compiler-sfc/compileScript.ts                                                                                                                                                                                   732          636           40           56\n packages/compiler-core/transforms/vFor.ts                                                                                                                                                                                212          180            5           27\n packages/compiler-core/transforms/vBind.ts                                                                                                                                                                                19           15            1            3\n packages/compiler-core/utils.ts                                                                                                                                                                                          169          153            2           14\n packages/compiler-core/compile.ts                                                                                                                                                                                         54           43            3            8\n packages/compiler-core/babelUtils.ts                                                                                                                                                                                     253          163           57           33\n packages/compiler-core/transforms/transformElement.ts                                                                                                                                                                    311          255           21           35\n packages/reactivity/baseHandler.ts                                                                                                                                                                                        78           68            0           10\n packages/compiler-core/ast.ts                                                                                                                                                                                            402          329           20           53\n packages/compiler-core/transform.ts                                                                                                                                                                                      215          194            4           17\n packages/compiler-core/codegen.ts                                                                                                                                                                                        413          368            6           39\n packages/compiler-core/parse.ts                                                                                                                                                                                          572          472           26           74\n=====================================================================================================================================================================================================================================================================\n Total                                                                                                                                                                                                        92         9023         7600          383         1040\n=====================================================================================================================================================================================================================================================================\n"
  },
  {
    "path": "tools/chibivue-playground/main.ts",
    "content": "import fs from \"node:fs\";\nimport fse from \"fs-extra\";\n\nconst red = (s: string) => `\\x1b[31m${s}\\x1b[0m`;\nconst green = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\nconst blue = (s: string) => `\\x1b[34m${s}\\x1b[0m`;\n\nconst targetDirPath = \"examples/playground\";\n\n// check if target path is empty\nif (!targetDirPath) {\n  console.log(\"Please specify the target path!\");\n  process.exit(1);\n}\n\n// check if target path exists. auto create if not\nif (fs.existsSync(targetDirPath)) {\n  const files = fs.readdirSync(targetDirPath);\n  if (files.length) {\n    console.log(\"\");\n    console.log(`${red(\"Oops!\")} Target directory is not empty!`);\n    console.log(\"\");\n    process.exit(1);\n  }\n} else {\n  fs.mkdirSync(targetDirPath, { recursive: true });\n}\n\n// copy template files\nconst templateDirPath = `tools/chibivue-playground/template`;\nfse.copySync(templateDirPath, targetDirPath);\n\n// message\nconsole.log(\"\");\nconsole.log(\"----------------------------------------------------------\");\nconsole.log(`${green(\"chibivue project created!\")} ${blue(\">>>\")} ${targetDirPath}`);\nconsole.log(`Enjoy your learning! 😎`);\nconsole.log(\"----------------------------------------------------------\");\n"
  },
  {
    "path": "tools/chibivue-playground/template/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "tools/chibivue-playground/template/package.json",
    "content": "{\n  \"name\": \"@examples/playground\",\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "tools/chibivue-playground/template/src/App.vue",
    "content": "<script setup>\nimport { useRouter } from 'chibivue-router'\nconst router = useRouter()\n</script>\n\n<template>\n  <div>\n    <nav>\n      <button @click=\"router.push('/')\">Top</button>\n      <button @click=\"router.push('/store')\">Store</button>\n    </nav>\n    <RouterView />\n  </div>\n</template>\n\n<style></style>\n"
  },
  {
    "path": "tools/chibivue-playground/template/src/main.ts",
    "content": "// @ts-nocheck\nimport { createApp } from \"chibivue\";\nimport { createStore } from \"chibivue-store\";\nimport App from \"./App.vue\";\nimport { router } from \"./router\";\n\nconst app = createApp(App);\napp.use(router);\napp.use(createStore());\napp.mount(\"#app\");\n"
  },
  {
    "path": "tools/chibivue-playground/template/src/router.ts",
    "content": "// @ts-nocheck\nimport { createRouter, createWebHistory } from \"chibivue-router\";\nimport Counter from \"./views/counter.vue\";\nimport CounterStore from \"./views/store.vue\";\n\nexport const router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: \"/\", component: Counter },\n    { path: \"/store\", component: CounterStore },\n  ],\n});\n"
  },
  {
    "path": "tools/chibivue-playground/template/src/store/counter.ts",
    "content": "import { ref } from \"chibivue\";\nimport { defineStore } from \"chibivue-store\";\n\nexport const useCounterStore = defineStore(\"counter\", () => {\n  const count = ref(0);\n\n  const increment = () => {\n    count.value++;\n  };\n\n  const reset = () => {\n    count.value = 0;\n  };\n\n  return { count, increment, reset };\n});\n"
  },
  {
    "path": "tools/chibivue-playground/template/src/views/counter.vue",
    "content": "<script setup>\nimport { ref } from 'chibivue'\nconst count = ref(0)\n</script>\n\n<template>\n  <div id=\"pages-counter\">\n    <h1>Counter</h1>\n    <button @click=\"count++\">increment</button>\n    <p>{{ count }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "tools/chibivue-playground/template/src/views/store.vue",
    "content": "<script setup>\nimport { useCounterStore } from '../store/counter'\nconst { count, increment } = useCounterStore()\n</script>\n\n<template>\n  <div id=\"pages-counter-store\">\n    <h1>Counter Store</h1>\n    <button @click=\"increment\">increment</button>\n    <p>{{ count }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "tools/chibivue-playground/template/vite.config.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\n// @ts-expect-error\nimport Chibivue from \"../../packages/@extensions/vite-plugin-chibivue/dist\";\n\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\nconst resolve = (p: string) => path.resolve(dirname, \"../../packages\", p);\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: resolve(\"chibivue/src\"),\n      \"@chibivue/runtime-core\": resolve(\"runtime-core/src\"),\n      \"@chibivue/runtime-dom\": resolve(\"runtime-dom/src\"),\n      \"@chibivue/runtime-vapor\": resolve(\"runtime-vapor/src\"),\n      \"@chibivue/reactivity\": resolve(\"reactivity/src\"),\n      \"@chibivue/shared\": resolve(\"shared/src\"),\n      \"@chibivue/compiler-core\": resolve(\"compiler-core/src\"),\n      \"@chibivue/compiler-dom\": resolve(\"compiler-dom/src\"),\n      \"@chibivue/compiler-sfc\": resolve(\"compiler-sfc/src\"),\n      \"chibivue-router\": resolve(\"@extensions/chibivue-router/src\"),\n      \"chibivue-store\": resolve(\"@extensions/chibivue-store/src\"),\n      \"vite-plugin-chibivue\": resolve(\"@extensions/vite-plugin-chibivue/src\"),\n    },\n  },\n  plugins: [Chibivue()],\n});\n"
  },
  {
    "path": "tools/create-chibivue/main.ts",
    "content": "import fs from \"node:fs\";\nimport fse from \"fs-extra\";\n\nconst red = (s: string) => `\\x1b[31m${s}\\x1b[0m`;\nconst green = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\nconst blue = (s: string) => `\\x1b[34m${s}\\x1b[0m`;\n\nconst targetDirPath = process.argv[2];\n\n// check if target path is empty\nif (!targetDirPath) {\n  console.log(\"Please specify the target path!\");\n  process.exit(1);\n}\n\n// check if target path exists. auto create if not\nif (fs.existsSync(targetDirPath)) {\n  const files = fs.readdirSync(targetDirPath);\n  if (files.length) {\n    console.log(\"\");\n    console.log(`${red(\"Oops!\")} Target directory is not empty!`);\n    console.log(\"\");\n    process.exit(1);\n  }\n} else {\n  fs.mkdirSync(targetDirPath, { recursive: true });\n}\n\n// copy template files\nconst templateDirPath = `tools/create-chibivue/template`;\nfse.copySync(templateDirPath, targetDirPath);\n\n// message\nconsole.log(\"\");\nconsole.log(\"----------------------------------------------------------\");\nconsole.log(`${green(\"chibivue project created!\")} ${blue(\">>>\")} ${targetDirPath}`);\nconsole.log(`Enjoy your learning! 😎`);\nconsole.log(\"----------------------------------------------------------\");\n"
  },
  {
    "path": "tools/create-chibivue/template/examples/playground/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "tools/create-chibivue/template/examples/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>chibivue</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "tools/create-chibivue/template/examples/playground/package.json",
    "content": "{\n  \"name\": \"playground\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.7.2\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "tools/create-chibivue/template/examples/playground/src/main.ts",
    "content": "import { helloChibivue } from \"chibivue\";\n\nhelloChibivue();\n"
  },
  {
    "path": "tools/create-chibivue/template/examples/playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"Node\",\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"chibivue\": [\"../../packages\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "tools/create-chibivue/template/examples/playground/vite.config.js",
    "content": "import { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: `${process.cwd()}/../../packages`,\n    },\n  },\n});\n"
  },
  {
    "path": "tools/create-chibivue/template/package.json",
    "content": "{\n  \"name\": \"chibivue\",\n  \"version\": \"0.0.0\",\n  \"description\": \"\",\n  \"scripts\": {\n    \"dev\": \"cd examples/playground && pnpm i && pnpm run dev\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^22.10.1\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "tools/create-chibivue/template/packages/index.ts",
    "content": "export const helloChibivue = () => {\n  console.log(\"Hello chibivue!\");\n};\n"
  },
  {
    "path": "tools/create-chibivue/template/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"strict\": true,\n    \"paths\": {\n      \"chibivue\": [\"./packages\"]\n    },\n    \"moduleResolution\": \"node\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"packages/**/*.ts\", \"examples/**/**.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "tools/translator/ja2en/completion.ts",
    "content": "import fs from \"node:fs\";\nimport consola from \"consola\";\nimport dotenv from \"dotenv\";\nimport { INPUT, OUT } from \"./constant\";\n\nexport const completion = async () => {\n  if (!fs.existsSync(\".env\")) {\n    consola.error(\".env not found!\");\n    return;\n  }\n\n  dotenv.config();\n  if (!process.env.OPEN_AI_API_KEY) {\n    consola.error(\"OPEN_AI_API_KEY not found!\");\n    return;\n  }\n\n  const createPrompt = (content: string) =>\n    `以下の日本語の文章を英語に翻訳してください。\\n\\n${content}`;\n\n  // check input.md content\n  const input = fs.readFileSync(INPUT, \"utf-8\");\n  if (!input) {\n    consola.error(\"input.md is empty!\");\n    return;\n  }\n\n  // print loader animation\n  const states = [\"|\", \"/\", \"-\", \"\\\\\"];\n  let i = 0;\n  const loader = setInterval(() => {\n    process.stdout.write(`\\r${states[i++ % states.length]} Translating...`);\n  }, 100);\n  const res = await post(createPrompt(input), process.env.OPEN_AI_API_KEY!);\n  clearInterval(loader);\n  if (!res) {\n    consola.error(\"Translation failed!\");\n    return;\n  }\n\n  fs.writeFileSync(OUT, res);\n  consola.log(\"\");\n  consola.success(`Translation completed! See ${OUT}`);\n};\n\ninterface ChatGPTResponse {\n  id: string;\n  object: string;\n  created: number;\n  model: string;\n  usage: {\n    prompt_tokens: number;\n    completion_tokens: number;\n    total_tokens: number;\n  };\n  choices: Choice[];\n}\n\ninterface ChoiceBase {\n  finish_reason: string;\n  index: number;\n}\ninterface Choice extends ChoiceBase {\n  message?: { role: string; content: string };\n}\n\nconst post = async (prompt: string, apiKey: string): Promise<string> => {\n  return await fetch(\"https://api.openai.com/v1/chat/completions\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n      Authorization: `Bearer ${apiKey}`,\n    },\n    body: JSON.stringify({\n      model: \"gpt-3.5-turbo\",\n      messages: [{ role: \"user\", content: prompt }],\n      temperature: 0.1,\n    }),\n  })\n    .then((res) => res.json())\n    .then((res: ChatGPTResponse) => res.choices[0]?.message?.content.trim() ?? \"\");\n};\n"
  },
  {
    "path": "tools/translator/ja2en/constant.ts",
    "content": "export const OUT = \"tools/translator/ja2en/output.md\";\nexport const INPUT = \"tools/translator/ja2en/input.md\";\n"
  },
  {
    "path": "tools/translator/ja2en/init.ts",
    "content": "import fs from \"node:fs\";\nimport readline from \"node:readline\";\nimport consola from \"consola\";\nimport dotenv from \"dotenv\";\nimport { INPUT } from \"./constant\";\n\nexport const init = async () => {\n  //  check .env\n  if (!fs.existsSync(\".env\")) {\n    consola.warn(\".env not found!\");\n    const rl = readline.createInterface({\n      input: process.stdin,\n      output: process.stdout,\n    });\n    const statue = await new Promise<\"continue\" | \"exit\">((resolve) => {\n      rl.question(\"Create .env? (y/n) \", (answer) => {\n        if ([\"\", \"Y\", \"y\", \"Yes\", \"yes\", \"YES\"].includes(answer)) {\n          fs.writeFileSync(\".env\", \"OPEN_AI_API_KEY= # TODO: your key\");\n          consola.success(\".env created!\");\n          resolve(\"continue\");\n          rl.close();\n        } else {\n          resolve(\"exit\");\n          rl.close();\n        }\n      });\n    });\n    if (statue === \"exit\") {\n      consola.fail(\"Please create .env first!\");\n      return;\n    }\n  }\n\n  // check process.env.OPEN_AI_API_KEY\n  // load .env\n  dotenv.config();\n  if (!process.env.OPEN_AI_API_KEY) {\n    consola.error(\"OPEN_AI_API_KEY not found!\");\n    return;\n  }\n\n  // check input.md\n  if (fs.existsSync(INPUT)) {\n    consola.warn(\"input.md already exists!\");\n  } else {\n    fs.writeFileSync(INPUT, \"\");\n    consola.success(\"input.md created!\");\n  }\n};\n"
  },
  {
    "path": "tools/translator/ja2en/main.ts",
    "content": "import { defineCommand, runMain } from \"citty\";\nimport { init } from \"./init\";\nimport { completion } from \"./completion\";\n\nconst main = defineCommand({\n  meta: { name: \"ja2en\" },\n  args: { init: { type: \"positional\", required: false } },\n  async run({ args }) {\n    (args.init ? init : completion)();\n  },\n});\n\nrunMain(main);\n"
  },
  {
    "path": "tools/vue-playground/generate.ts",
    "content": "import fs from \"node:fs/promises\";\nimport { listFiles } from \"./helpers\";\nimport fse from \"fs-extra\";\n\nexport const generate = (\n  templateDirPath: string,\n  targetDirPath: string,\n  vars: Map<string, string>,\n): Promise<void> =>\n  new Promise(async (resolve) => {\n    fse.copySync(templateDirPath, targetDirPath);\n    const templateFiles = listFiles(targetDirPath).filter((f) => f.endsWith(\".template\"));\n    await Promise.all(\n      templateFiles.map(async (templateFile) => {\n        const targetFile = templateFile.replace(/\\.template$/, \"\");\n        const template = await fs.readFile(templateFile, \"utf-8\");\n        const embeddedTemplate = embedVars(template, vars);\n        await fs.writeFile(targetFile, embeddedTemplate);\n        await fs.unlink(templateFile);\n      }),\n    );\n    resolve();\n  });\n\nconst embedVars = (template: string, vars: Map<string, string>): string => {\n  let result = template;\n  vars.forEach((value, key) => {\n    result = result.replace(new RegExp(`<%= ${key} %>`, \"g\"), value);\n  });\n  return result;\n};\n"
  },
  {
    "path": "tools/vue-playground/helpers.ts",
    "content": "import fs from \"node:fs\";\n\nexport const isAbsolutePath = (path: string) => path.startsWith(\"/\") || path.startsWith(\"C:\\\\\");\n\nexport const trimTrailingSlash = (path: string) => (path.endsWith(\"/\") ? path.slice(0, -1) : path);\n\nexport const listFiles = (dir: string): string[] =>\n  fs\n    .readdirSync(dir, { withFileTypes: true })\n    .flatMap((dirent) =>\n      dirent.isFile() ? [`${dir}/${dirent.name}`] : listFiles(`${dir}/${dirent.name}`),\n    );\n"
  },
  {
    "path": "tools/vue-playground/locale.ts",
    "content": "import { t } from \"chainsi\";\n\nexport const locale = {\n  prompts: {\n    requestVuejsCorePath: {\n      // prettier-ignore\n      $t: () =>\n        `${t('💁').blue.bold._} input your local vuejs/core ${t('absolute').green.bold.underline._} path:\\n` +\n        `  ${t('e.g. /Users/ubugeeei/oss/vuejs-core\\n').grey._}` +\n        `  > `,\n    },\n    confirmUseRootPath: {\n      $t: () =>\n        // prettier-ignore\n        `${t('Are you sure to use root path?').red._} ${t('(Y/n)').grey._}\\n` +\n        `  > `,\n    },\n  },\n  success: {\n    generate: {\n      $t: (targetPath: string) =>\n        // prettier-ignore\n        `\\n==================================================================\\n\\n` +\n        `${t('🎉 vuejs/core hmr project was created successfully!').green._}\\n` +\n        `  (${t('>>>').blue._} ${t(targetPath).green._})\\n\\n` +\n        `${t('You can start by').grey._} ${t('nr dev:vue').cyan.bold.underline._}\\n`+\n        `\\n==================================================================\\n\\n` +\n        `${t('Enjoy your learning! 😎').green._}`,\n    },\n  },\n  warnings: {\n    rootPath: {\n      $t: () =>\n        // prettier-ignore\n        `${t('⚠️ You are using root path.').yellow._}\\n`,\n    },\n  },\n  errors: {\n    targetDirHasAlreadyExist: {\n      $t: () => `${t(\"[Error] Target directory has already exist!\").red._}\\n`,\n    },\n    noEmptyPathInput: {\n      $t: () => `${t(\"[Invalid input] empty input is not allowed.\").red._}\\n`,\n    },\n    noRelativePathInput: {\n      $t: () =>\n        // prettier-ignore\n        `${t('[Invalid input] input must be an').red._} ${t('absolute').yellow.bold.underline._} ${t('path').red._}\\n`,\n    },\n  },\n};\n"
  },
  {
    "path": "tools/vue-playground/main.ts",
    "content": "import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { fileURLToPath } from \"node:url\";\n\nimport { locale } from \"./locale\";\nimport { getVuejsCorePathInteract } from \"./prompt\";\nimport { generate } from \"./generate\";\n\nconst TARGET_DIR_PATH = \"examples/vuejs-core\";\nconst dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));\n\nawait (async function main() {\n  checkTargetDirExist();\n  const templateVars = new Map<string, string>();\n  const userInputVuejsCorePath = await getVuejsCorePathInteract();\n  templateVars.set(\"vuejs_core_absolute_path\", userInputVuejsCorePath);\n  const templateDirPath = path.resolve(dirname, \"./template\");\n  await generate(templateDirPath, TARGET_DIR_PATH, templateVars);\n  console.log(locale.success.generate.$t(`./${TARGET_DIR_PATH}`));\n})();\n\nfunction checkTargetDirExist() {\n  if (fs.existsSync(TARGET_DIR_PATH)) {\n    const files = fs.readdirSync(TARGET_DIR_PATH);\n    if (files.length) {\n      console.log(`\\n${locale.errors.targetDirHasAlreadyExist.$t()}\\n`);\n      process.exit(1);\n    }\n  } else {\n    fs.mkdirSync(TARGET_DIR_PATH, { recursive: true });\n  }\n}\n"
  },
  {
    "path": "tools/vue-playground/prompt.ts",
    "content": "import { createInterface } from \"node:readline\";\nimport { locale } from \"./locale\";\nimport { isAbsolutePath, trimTrailingSlash } from \"./helpers\";\n\nexport const getVuejsCorePathInteract = async () =>\n  new Promise<string>((resolve) => {\n    process.stdout.write(locale.prompts.requestVuejsCorePath.$t());\n    let userInputVuejsCorePath = \"\";\n    const rl = createInterface({\n      input: process.stdin,\n    });\n    rl.on(\"line\", (line) => {\n      const trimmedLine = line.trim();\n      if (trimmedLine) {\n        if (isAbsolutePath(trimmedLine)) {\n          const trimmedPath = trimTrailingSlash(trimmedLine);\n          userInputVuejsCorePath = trimmedPath;\n          rl.close();\n          resolve(userInputVuejsCorePath);\n        } else {\n          process.stdout.write(`  ${locale.errors.noRelativePathInput.$t()}`);\n        }\n      } else {\n        process.stdout.write(`  ${locale.errors.noEmptyPathInput.$t()}`);\n        process.stdout.write(locale.prompts.requestVuejsCorePath.$t());\n      }\n    });\n  });\n"
  },
  {
    "path": "tools/vue-playground/template/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>vuejs/core</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n\n    <script type=\"module\" src=\"./src/main.ts\"></script>\n\n    <!-- OR -->\n    <!-- <script type=\"module\">\n      import { createApp, ref } from \"./vue-standalone.js\";\n      createApp({\n        setup() {\n          const count = ref(0);\n          return { count };\n        },\n        template: `<button @click=\"count++\">{{ count }}</button>`\n      }).mount(\"#app\");\n    </script> -->\n</html>\n"
  },
  {
    "path": "tools/vue-playground/template/package.json.template",
    "content": "{\n  \"name\": \"@examples/vuejs-core\",\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"tsx setup/start\"\n  },\n  \"dependencies\": {\n    \"vue\": \"<%= vuejs_core_absolute_path %>\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue\": \"^6.0.5\",\n    \"vite\": \"^8.0.0\",\n    \"vite-hyper-config\": \"^0.8.1\",\n    \"vite-node\": \"^5.3.0\"\n  }\n}\n"
  },
  {
    "path": "tools/vue-playground/template/setup/dev.ts.template",
    "content": "//! Copied from [vuejs/core-vapor/playground/setup/dev.js](https://github.com/vuejs/core-vapor/blob/bdf28de8e83cc8e398768eedfc0ac932b6a334ab/playground/setup/dev.js)\n\nimport path from 'node:path'\n\nconst resolve = (p: string) =>\n  path.resolve('<%= vuejs_core_absolute_path %>', 'packages', p)\n\nexport function DevPlugin({ browser = false } = {}) {\n  return {\n    name: 'dev-plugin',\n    config() {\n      return {\n        resolve: {\n          alias: {\n            vue: resolve('vue/src/runtime.ts'),\n\n            '@vue/runtime-core': resolve('runtime-core/src'),\n            '@vue/runtime-dom': resolve('runtime-dom/src'),\n\n            '@vue/compiler-core': resolve('compiler-core/src'),\n            '@vue/compiler-dom': resolve('compiler-dom/src'),\n\n            '@vue/compiler-sfc': resolve('compiler-sfc/src'),\n            '@vue/compiler-ssr': resolve('compiler-ssr/src'),\n\n            '@vue/reactivity': resolve('reactivity/src'),\n            '@vue/shared': resolve('shared/src'),\n          },\n        },\n        define: {\n          __COMMIT__: `\"__COMMIT__\"`,\n          __VERSION__: `\"0.0.0\"`,\n          __DEV__: `true`,\n          // this is only used during Vue's internal tests\n          __TEST__: `false`,\n          // If the build is expected to run directly in the browser (global / esm builds)\n          __BROWSER__: String(browser),\n          __GLOBAL__: String(false),\n          __ESM_BUNDLER__: String(true),\n          __ESM_BROWSER__: String(false),\n          // is targeting Node (SSR)?\n          __NODE_JS__: String(false),\n          // need SSR-specific branches?\n          __SSR__: String(false),\n\n          // 2.x compat build\n          __COMPAT__: String(false),\n\n          // feature flags\n          __FEATURE_SUSPENSE__: `true`,\n          __FEATURE_OPTIONS_API__: `true`,\n          __FEATURE_PROD_DEVTOOLS__: `false`,\n        },\n      }\n    },\n  }\n}\n"
  },
  {
    "path": "tools/vue-playground/template/setup/start.ts",
    "content": "//! Copied from [vuejs/core-vapor/playground/setup/vite.js](https://github.com/vuejs/core-vapor/blob/bdf28de8e83cc8e398768eedfc0ac932b6a334ab/playground/setup/vite.js)\nimport { DevPlugin } from \"./dev\";\nimport { startVite } from \"vite-hyper-config\";\n\nstartVite(undefined, { plugins: [DevPlugin()] }, { deps: { inline: [\"@vitejs/plugin-vue\"] } });\n"
  },
  {
    "path": "tools/vue-playground/template/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\nconst count = ref(0)\n</script>\n\n<template>\n  <button @click=\"count++\">{{ count }}</button>\n</template>\n"
  },
  {
    "path": "tools/vue-playground/template/src/main.ts",
    "content": "import { createApp } from \"vue\";\n\n// @ts-expect-error\nconst modules = import.meta.glob(\"./*.(vue|js)\");\nconst mod = (modules[\".\" + location.pathname] || modules[\"./App.vue\"])();\n\nmod.then(({ default: mod }: any) => createApp(mod).mount(\"#app\"));\n"
  },
  {
    "path": "tools/vue-playground/template/tsconfig.json.template",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"strict\": true,\n    \"paths\": {\n      \"vue\": [\n        \"<%= vuejs_core_absolute_path %>/packages/vue/src\"\n      ],\n      \"@vue/*\": [\n        \"<%= vuejs_core_absolute_path %>/packages/*/src\"\n      ],\n    },\n    \"allowJs\": true,\n    \"esModuleInterop\": true,\n    \"moduleResolution\": \"Bundler\",\n  },\n  \"include\": [\n    \"src\",\n    \"vite.config.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n  ]\n}"
  },
  {
    "path": "tools/vue-playground/template/vite.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport Vue from \"@vitejs/plugin-vue\";\nimport * as Compiler from \"@vue/compiler-core\";\nimport * as CompilerSFC from \"@vue/compiler-sfc\";\nimport { DevPlugin } from \"./setup/dev\";\n\nexport default defineConfig({\n  build: {\n    target: \"esnext\",\n  },\n  clearScreen: false,\n  plugins: [Vue({ template: Compiler as any, compiler: CompilerSFC }), DevPlugin()],\n});\n"
  },
  {
    "path": "tools/vue-playground/template/vue-standalone.js.template",
    "content": "export * from '<%= vuejs_core_absolute_path %>/packages/vue/src'"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"declaration\": true,\n    \"isolatedDeclarations\": true,\n    \"verbatimModuleSyntax\": true,\n    \"skipLibCheck\": true,\n    \"noEmit\": true,\n    \"paths\": {\n      \"@chibivue/*\": [\"./impl/*/src\"],\n      \"chibivue\": [\"./impl/chibivue/src\"],\n      \"chibivue-store\": [\"./impl/@extensions/chibivue-store/src\"],\n      \"chibivue-router\": [\"./impl/@extensions/chibivue-router/src\"],\n      \"vite-plugin-chibivue\": [\"./impl/@extensions/vite-plugin-chibivue/src\"]\n    }\n  },\n  \"include\": [\"impl/**/*.ts\", \"examples/**/*.ts\", \"examples/**/*.vue\"],\n  \"exclude\": [\"**/node_modules\", \"**/dist\", \"examples/vuejs-core\"]\n}\n"
  },
  {
    "path": "vite.config.shared.ts",
    "content": "import path from \"node:path\";\nimport { defineConfig } from \"vite\";\n// @ts-expect-error - dts outputs hashed filenames\nimport Chibivue from \"./packages/@extensions/vite-plugin-chibivue/dist\";\n\nconst resolve = (p: string) => path.resolve(import.meta.dirname, \"packages\", p);\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      chibivue: resolve(\"chibivue/src\"),\n      \"@chibivue/runtime-core\": resolve(\"runtime-core/src\"),\n      \"@chibivue/runtime-dom\": resolve(\"runtime-dom/src\"),\n      \"@chibivue/runtime-vapor\": resolve(\"runtime-vapor/src\"),\n      \"@chibivue/reactivity\": resolve(\"reactivity/src\"),\n      \"@chibivue/shared\": resolve(\"shared/src\"),\n      \"@chibivue/compiler-core\": resolve(\"compiler-core/src\"),\n      \"@chibivue/compiler-dom\": resolve(\"compiler-dom/src\"),\n      \"@chibivue/compiler-sfc\": resolve(\"compiler-sfc/src\"),\n      \"chibivue-router\": resolve(\"@extensions/chibivue-router/src\"),\n      \"chibivue-store\": resolve(\"@extensions/chibivue-store/src\"),\n    },\n  },\n  plugins: [Chibivue()],\n});\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from \"vitest/config\";\nimport { playwright } from \"@vitest/browser-playwright\";\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    include: [\"**/tests/**/*.spec.ts\"],\n    browser: {\n      enabled: true,\n      provider: playwright(),\n      instances: [{ browser: \"chromium\" }],\n    },\n  },\n});\n"
  }
]